Merge branch 'main' into js/shared-dataflow

This commit is contained in:
Asger F
2024-06-25 11:48:41 +02:00
2477 changed files with 100491 additions and 79725 deletions

View File

@@ -0,0 +1 @@
These tests are still run with the legacy test runner

View File

@@ -1,3 +1,25 @@
## 1.0.1
No user-facing changes.
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### Minor Analysis Improvements
* Additional heuristics for a new sensitive data classification for private information (e.g. credit card numbers) have been added to the shared `SensitiveDataHeuristics.qll` library. This may result in additional results for queries that use sensitive data such as `js/clear-text-storage-sensitive-data` and `js/clear-text-logging`.
### Bug Fixes
* Fixed a bug where very large TypeScript files would cause database creation to crash. Large files over 10MB were already excluded from analysis, but the file size check was not applied to TypeScript files.
## 0.9.1
No user-facing changes.
## 0.9.0
### Breaking Changes

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Enabled type-tracking to follow content through array methods
* Improved modeling of `Array.prototype.splice` for when it is called with more than two arguments

View File

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

View File

@@ -0,0 +1,13 @@
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### Minor Analysis Improvements
* Additional heuristics for a new sensitive data classification for private information (e.g. credit card numbers) have been added to the shared `SensitiveDataHeuristics.qll` library. This may result in additional results for queries that use sensitive data such as `js/clear-text-storage-sensitive-data` and `js/clear-text-logging`.
### Bug Fixes
* Fixed a bug where very large TypeScript files would cause database creation to crash. Large files over 10MB were already excluded from analysis, but the file size check was not applied to TypeScript files.

View File

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.9.0
lastReleaseVersion: 1.0.1

View File

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

View File

@@ -77,8 +77,12 @@ module ArrayTaintTracking {
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = ["push", "unshift"]
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
pred = call.getArgument(2) and
// `array.splice(i, del, e1, e2, ...)`: if any item is tainted, then so is `array`.
pred = call.getArgument(any(int i | i >= 2)) and
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
or
// `array.splice(i, del, ...e)`: if `e` is tainted, then so is `array`.
pred = call.getASpreadArgument() and
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
@@ -115,9 +119,9 @@ private module ArrayDataFlow {
* A step modeling the creation of an Array using the `Array.from(x)` method.
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
*/
private class ArrayFrom extends DataFlow::LegacyFlowStep {
private class ArrayFrom extends LegacyPreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
exists(DataFlow::CallNode call |
call = arrayFromCall() and
@@ -135,9 +139,9 @@ private module ArrayDataFlow {
*
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
*/
private class ArrayCopySpread extends DataFlow::LegacyFlowStep {
private class ArrayCopySpread extends LegacyPreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
fromProp = arrayLikeElement() and
toProp = arrayElement() and
@@ -156,7 +160,7 @@ private module ArrayDataFlow {
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::LegacyFlowStep {
private class ArrayAppendStep extends LegacyPreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
exists(DataFlow::MethodCallNode call |
@@ -187,7 +191,7 @@ private module ArrayDataFlow {
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
*/
private class ArrayIndexingStep extends DataFlow::LegacyFlowStep {
private class ArrayIndexingStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(ArrayIndexingAccess access |
prop = arrayElement() and
@@ -209,7 +213,7 @@ private module ArrayDataFlow {
* A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`.
* E.g. `array.pop()`.
*/
private class ArrayPopStep extends DataFlow::LegacyFlowStep {
private class ArrayPopStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["pop", "shift", "at"] and
@@ -274,25 +278,38 @@ private module ArrayDataFlow {
/**
* A step modeling that `splice` can insert elements into an array.
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
* For example in `array.splice(i, del, e1, e2, ...)`: if any item is tainted, then so is `array`
*/
private class ArraySpliceStep extends DataFlow::LegacyFlowStep {
private class ArraySpliceStep extends LegacyPreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "splice" and
prop = arrayElement() and
element = call.getArgument(2) and
element = call.getArgument(any(int i | i >= 2)) and
call = obj.getAMethodCall()
)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
fromProp = arrayLikeElement() and
toProp = arrayElement() and
// `array.splice(i, del, ...arr)` variant
exists(DataFlow::MethodCallNode mcn |
mcn.getMethodName() = "splice" and
pred = mcn.getASpreadArgument() and
succ = mcn.getReceiver().getALocalSource()
)
}
}
/**
* A step for modeling `concat`.
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
*/
private class ArrayConcatStep extends DataFlow::LegacyFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
private class ArrayConcatStep extends LegacyPreCallGraphStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "concat" and
prop = arrayElement() and
@@ -305,8 +322,8 @@ private module ArrayDataFlow {
/**
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::LegacyFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
private class ArraySliceStep extends LegacyPreCallGraphStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["slice", "splice", "filter"] and
prop = arrayElement() and
@@ -319,7 +336,7 @@ private module ArrayDataFlow {
/**
* A step modeling that elements from an array `arr` are received by calling `find`.
*/
private class ArrayFindStep extends DataFlow::LegacyFlowStep {
private class ArrayFindStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::CallNode call |
call = arrayFindCall(pred) and
@@ -382,7 +399,7 @@ private module ArrayLibraries {
* E.g. `array-union` that creates a union of multiple arrays, or `array-uniq` that creates an array with unique elements.
*/
DataFlow::CallNode arrayCopyCall(DataFlow::Node array) {
result = API::moduleImport(["array-union", "array-uniq", "uniq"]).getACall() and
result = DataFlow::moduleImport(["array-union", "array-uniq", "uniq"]).getACall() and
array = result.getAnArgument()
}
@@ -401,8 +418,8 @@ private module ArrayLibraries {
/**
* A loadStoreStep for a library that copies the elements of an array into another array.
*/
private class ArrayCopyLoadStore extends DataFlow::LegacyFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
private class ArrayCopyLoadStore extends LegacyPreCallGraphStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::CallNode call |
call = arrayCopyCall(pred) and
succ = call and

View File

@@ -63,6 +63,12 @@ private module Cached {
TSynthCaptureNode(VariableCapture::VariableCaptureOutput::SynthesizedCaptureNode node) or
TGenericSynthesizedNode(AstNode node, string tag, DataFlowPrivate::DataFlowCallable container) {
any(AdditionalFlowInternal flow).needsSynthesizedNode(node, tag, container)
} or
TForbiddenRecursionGuard() {
none() and
// We want to prune irrelevant models before materialising data flow nodes, so types contributed
// directly from CodeQL must expose their pruning info without depending on data flow nodes.
(any(ModelInput::TypeModel tm).isTypeUsed("") implies any())
}
cached

View File

@@ -1003,7 +1003,7 @@ module NodeJSLib {
exists(ClientRequestLoginCallback callback | this = callback.getACall().getArgument(0))
}
override string getCredentialsKind() { result = "Node.js http(s) client login username" }
override string getCredentialsKind() { result = "user name" }
}
/**
@@ -1014,7 +1014,7 @@ module NodeJSLib {
exists(ClientRequestLoginCallback callback | this = callback.getACall().getArgument(1))
}
override string getCredentialsKind() { result = "Node.js http(s) client login password" }
override string getCredentialsKind() { result = "password" }
}
/**

View File

@@ -1,15 +1,29 @@
/**
* Models the `shelljs` library in terms of `FileSystemAccess` and `SystemCommandExecution`.
*
* https://www.npmjs.com/package/shelljs
*/
import javascript
module ShellJS {
private API::Node shellJSMember() {
result = API::moduleImport("shelljs")
or
result =
shellJSMember()
.getMember([
"exec", "cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv",
"rm", "cat", "head", "sort", "tail", "uniq", "grep", "sed", "to", "toEnd", "echo"
])
.getReturn()
}
/**
* Gets an import of the `shelljs` or `async-shelljs` module.
* Gets a function that can execute a shell command using the `shelljs` or `async-shelljs` modules.
*/
DataFlow::SourceNode shelljs() {
result = DataFlow::moduleImport("shelljs") or
result = shellJSMember().asSource() or
result = DataFlow::moduleImport("async-shelljs")
}
@@ -39,7 +53,10 @@ module ShellJS {
/** The `shelljs.exec` library modeled as a `shelljs` member. */
private class ShellJsExec extends Range {
ShellJsExec() { this = DataFlow::moduleImport("shelljs.exec") }
ShellJsExec() {
this = DataFlow::moduleImport("shelljs.exec") or
this = shellJSMember().getMember("exec").asSource()
}
override string getName() { result = "exec" }
}

View File

@@ -168,9 +168,20 @@ module ModelInput {
* A unit class for adding additional type model rows from CodeQL models.
*/
class TypeModel extends Unit {
/**
* Holds if any of the other predicates in this class might have a result
* for the given `type`.
*
* The implementation of this predicate should not depend on `DataFlow::Node`.
*/
bindingset[type]
predicate isTypeUsed(string type) { none() }
/**
* Gets a data-flow node that is a source of the given `type`.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the source.
*/
@@ -180,6 +191,8 @@ module ModelInput {
* Gets a data-flow node that is a sink of the given `type`,
* usually because it is an argument passed to a parameter of that type.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the sink.
*/
@@ -188,6 +201,8 @@ module ModelInput {
/**
* Gets an API node that is a source or sink of the given `type`.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* Unlike `getASource` and `getASink`, this may depend on API graphs.
*/
API::Node getAnApiNode(string type) { none() }
@@ -354,6 +369,28 @@ private predicate typeVariableModel(string name, string path) {
Extensions::typeVariableModel(name, path)
}
/**
* Holds if the given extension tuple `madId` should pretty-print as `model`.
*
* This predicate should only be used in tests.
*/
predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
exists(string type, string path, string kind |
Extensions::sourceModel(type, path, kind, madId) and
model = "Source: " + type + "; " + path + "; " + kind
)
or
exists(string type, string path, string kind |
Extensions::sinkModel(type, path, kind, madId) and
model = "Sink: " + type + "; " + path + "; " + kind
)
or
exists(string type, string path, string input, string output, string kind |
Extensions::summaryModel(type, path, input, output, kind, madId) and
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
)
}
/**
* Holds if rows involving `type` might be relevant for the analysis of this database.
*/
@@ -367,6 +404,8 @@ predicate isRelevantType(string type) {
(
Specific::isTypeUsed(type)
or
any(TypeModel model).isTypeUsed(type)
or
exists(TestAllModels t)
)
or

View File

@@ -14,13 +14,14 @@
* - id: a user name or other account information;
* - password: a password or authorization key;
* - certificate: a certificate.
* - private: private data such as credit card numbers
*
* While classifications are represented as strings, this should not be relied upon.
* Instead, use the predicates in `SensitiveDataClassification::` to work with
* classifications.
*/
class SensitiveDataClassification extends string {
SensitiveDataClassification() { this in ["secret", "id", "password", "certificate"] }
SensitiveDataClassification() { this in ["secret", "id", "password", "certificate", "private"] }
}
/**
@@ -38,6 +39,9 @@ module SensitiveDataClassification {
/** Gets the classification for certificates. */
SensitiveDataClassification certificate() { result = "certificate" }
/** Gets the classification for private data. */
SensitiveDataClassification private() { result = "private" }
}
/**
@@ -77,6 +81,40 @@ module HeuristicNames {
*/
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name|ification)).*" }
/**
* Gets a regular expression that identifies strings that may indicate the presence of
* private data.
*/
string maybePrivate() {
result =
"(?is).*(" +
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
// Government identifiers, such as Social Security Numbers
"social.?security|employer.?identification|national.?insurance|resident.?id|" +
"passport.?(num|no)|([_-]|\\b)ssn([_-]|\\b)|" +
// Contact information, such as home addresses
"post.?code|zip.?code|home.?addr|" +
// and telephone numbers
"(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax|phone).?(num|no)|telephone|" +
"emergency.?contact|" +
// Geographic location - where the user is (or was)
"latitude|longitude|nationality|" +
// Financial data - such as credit card numbers, salary, bank accounts, and debts
"(credit|debit|bank|visa).?(card|num|no|acc(ou)?nt)|acc(ou)?nt.?(no|num|credit)|" +
"salary|billing|credit.?(rating|score)|([_-]|\\b)ccn([_-]|\\b)|" +
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
// "e(mail|_mail)|" + // this seems too noisy
// Health - medical conditions, insurance status, prescription records
"birth.?da(te|y)|da(te|y).?(of.?)?birth|" +
"medical|(health|care).?plan|healthkit|appointment|prescription|" +
"blood.?(type|alcohol|glucose|pressure)|heart.?(rate|rhythm)|body.?(mass|fat)|" +
"menstrua|pregnan|insulin|inhaler|" +
// Relationships - work and family
"employ(er|ee)|spouse|maiden.?name" +
// ---
").*"
}
/**
* Gets a regular expression that identifies strings that may indicate the presence
* of sensitive data, with `classification` describing the kind of sensitive data involved.
@@ -90,6 +128,9 @@ module HeuristicNames {
or
result = maybeCertificate() and
classification = SensitiveDataClassification::certificate()
or
result = maybePrivate() and
classification = SensitiveDataClassification::private()
}
/**

View File

@@ -1,4 +1,4 @@
description: Removed unused column from the `folders` and `files` relations
compatibility: full
files.rel: reorder files.rel (int id, string name, string simple, string ext, int fromSource) id name
folders.rel: reorder folders.rel (int id, string name, string simple) id name
files.rel: reorder files.rel (@file id, string name, string simple, string ext, int fromSource) id name
folders.rel: reorder folders.rel (@folder id, string name, string simple) id name

View File

@@ -1,94 +1,94 @@
description: Rename multiple relation names.
compatibility: backwards
is_externs.rel: reorder isExterns.rel(int toplevel) toplevel
is_externs.rel: reorder isExterns.rel(@toplevel toplevel) toplevel
isExterns.rel: delete
is_module.rel: reorder isModule.rel(int toplevel) toplevel
is_module.rel: reorder isModule.rel(@toplevel toplevel) toplevel
isModule.rel: delete
is_nodejs.rel: reorder isNodejs.rel(int toplevel) toplevel
is_nodejs.rel: reorder isNodejs.rel(@toplevel toplevel) toplevel
isNodejs.rel: delete
is_es2015_module.rel: reorder isES2015Module.rel(int toplevel) toplevel
is_es2015_module.rel: reorder isES2015Module.rel(@toplevel toplevel) toplevel
isES2015Module.rel: delete
is_closure_module.rel: reorder isClosureModule.rel(int toplevel) toplevel
is_closure_module.rel: reorder isClosureModule.rel(@toplevel toplevel) toplevel
isClosureModule.rel: delete
stmt_containers.rel: reorder stmtContainers.rel(int stmt, int container) stmt container
stmt_containers.rel: reorder stmtContainers.rel(@stmt stmt, @stmt_container container) stmt container
stmtContainers.rel: delete
jump_targets.rel: reorder jumpTargets.rel(int jump, int target) jump target
jump_targets.rel: reorder jumpTargets.rel(@stmt jump, @stmt target) jump target
jumpTargets.rel: delete
is_instantiated.rel: reorder isInstantiated.rel(int decl) decl
is_instantiated.rel: reorder isInstantiated.rel(@namespacedeclaration decl) decl
isInstantiated.rel: delete
has_declare_keyword.rel: reorder hasDeclareKeyword.rel(int stmt) stmt
has_declare_keyword.rel: reorder hasDeclareKeyword.rel(@declarablenode stmt) stmt
hasDeclareKeyword.rel: delete
is_for_await_of.rel: reorder isForAwaitOf.rel(int forof) forof
is_for_await_of.rel: reorder isForAwaitOf.rel(@forofstmt forof) forof
isForAwaitOf.rel: delete
enclosing_stmt.rel: reorder enclosingStmt.rel(int expr, int stmt) expr stmt
enclosing_stmt.rel: reorder enclosingStmt.rel(@exprortype expr, @stmt stmt) expr stmt
enclosingStmt.rel: delete
expr_containers.rel: reorder exprContainers.rel(int expr, int container) expr container
expr_containers.rel: reorder exprContainers.rel(@exprortype expr, @stmt_container container) expr container
exprContainers.rel: delete
array_size.rel: reorder arraySize.rel(int ae, int sz) ae sz
array_size.rel: reorder arraySize.rel(@arraylike ae, int sz) ae sz
arraySize.rel: delete
is_delegating.rel: reorder isDelegating.rel(int yield) yield
is_delegating.rel: reorder isDelegating.rel(@yieldexpr yield) yield
isDelegating.rel: delete
is_arguments_object.rel: reorder isArgumentsObject.rel(int id) id
is_arguments_object.rel: reorder isArgumentsObject.rel(@variable id) id
isArgumentsObject.rel: delete
is_computed.rel: reorder isComputed.rel(int prop) prop
is_computed.rel: reorder isComputed.rel(@property prop) prop
isComputed.rel: delete
is_method.rel: reorder isMethod.rel(int prop) prop
is_method.rel: reorder isMethod.rel(@property prop) prop
isMethod.rel: delete
is_static.rel: reorder isStatic.rel(int prop) prop
is_static.rel: reorder isStatic.rel(@property prop) prop
isStatic.rel: delete
is_abstract_member.rel: reorder isAbstractMember.rel(int prop) prop
is_abstract_member.rel: reorder isAbstractMember.rel(@property prop) prop
isAbstractMember.rel: delete
is_const_enum.rel: reorder isConstEnum.rel(int id) id
is_const_enum.rel: reorder isConstEnum.rel(@enumdeclaration id) id
isConstEnum.rel: delete
is_abstract_class.rel: reorder isAbstractClass.rel(int id) id
is_abstract_class.rel: reorder isAbstractClass.rel(@classdeclstmt id) id
isAbstractClass.rel: delete
has_public_keyword.rel: reorder hasPublicKeyword.rel(int prop) prop
has_public_keyword.rel: reorder hasPublicKeyword.rel(@property prop) prop
hasPublicKeyword.rel: delete
has_private_keyword.rel: reorder hasPrivateKeyword.rel(int prop) prop
has_private_keyword.rel: reorder hasPrivateKeyword.rel(@property prop) prop
hasPrivateKeyword.rel: delete
has_protected_keyword.rel: reorder hasProtectedKeyword.rel(int prop) prop
has_protected_keyword.rel: reorder hasProtectedKeyword.rel(@property prop) prop
hasProtectedKeyword.rel: delete
has_readonly_keyword.rel: reorder hasReadonlyKeyword.rel(int prop) prop
has_readonly_keyword.rel: reorder hasReadonlyKeyword.rel(@property prop) prop
hasReadonlyKeyword.rel: delete
has_type_keyword.rel: reorder hasTypeKeyword.rel(int id) id
has_type_keyword.rel: reorder hasTypeKeyword.rel(@import_or_export_declaration id) id
hasTypeKeyword.rel: delete
is_optional_member.rel: reorder isOptionalMember.rel(int id) id
is_optional_member.rel: reorder isOptionalMember.rel(@property id) id
isOptionalMember.rel: delete
has_definite_assignment_assertion.rel: reorder hasDefiniteAssignmentAssertion.rel(int id) id
has_definite_assignment_assertion.rel: reorder hasDefiniteAssignmentAssertion.rel(@field_or_vardeclarator id) id
hasDefiniteAssignmentAssertion.rel: delete
is_optional_parameter_declaration.rel: reorder isOptionalParameterDeclaration.rel(int parameter) parameter
is_optional_parameter_declaration.rel: reorder isOptionalParameterDeclaration.rel(@pattern parameter) parameter
isOptionalParameterDeclaration.rel: delete
has_asserts_keyword.rel: reorder hasAssertsKeyword.rel(int node) node
has_asserts_keyword.rel: reorder hasAssertsKeyword.rel(@predicatetypeexpr node) node
hasAssertsKeyword.rel: delete
js_parse_errors.rel: reorder jsParseErrors.rel(int id, int toplevel, string message, string line) id toplevel message line
js_parse_errors.rel: reorder jsParseErrors.rel(@js_parse_error id, @toplevel toplevel, string message, string line) id toplevel message line
jsParseErrors.rel: delete
regexp_parse_errors.rel: reorder regexpParseErrors.rel(int id, int regexp, string message) id regexp message
regexp_parse_errors.rel: reorder regexpParseErrors.rel(@regexp_parse_error id, @regexpterm regexp, string message) id regexp message
regexpParseErrors.rel: delete
is_greedy.rel: reorder isGreedy.rel(int id) id
is_greedy.rel: reorder isGreedy.rel(@regexp_quantifier id) id
isGreedy.rel: delete
range_quantifier_lower_bound.rel: reorder rangeQuantifierLowerBound.rel(int id, int lo) id lo
range_quantifier_lower_bound.rel: reorder rangeQuantifierLowerBound.rel(@regexp_range id, int lo) id lo
rangeQuantifierLowerBound.rel: delete
range_quantifier_upper_bound.rel: reorder rangeQuantifierUpperBound.rel(int id, int hi) id hi
range_quantifier_upper_bound.rel: reorder rangeQuantifierUpperBound.rel(@regexp_range id, int hi) id hi
rangeQuantifierUpperBound.rel: delete
is_capture.rel: reorder isCapture.rel(int id, int number) id number
is_capture.rel: reorder isCapture.rel(@regexp_group id, int number) id number
isCapture.rel: delete
is_named_capture.rel: reorder isNamedCapture.rel(int id, string name) id name
is_named_capture.rel: reorder isNamedCapture.rel(@regexp_group id, string name) id name
isNamedCapture.rel: delete
is_inverted.rel: reorder isInverted.rel(int id) id
is_inverted.rel: reorder isInverted.rel(@regexp_char_class id) id
isInverted.rel: delete
regexp_const_value.rel: reorder regexpConstValue.rel(int id, string value) id value
regexp_const_value.rel: reorder regexpConstValue.rel(@regexp_constant id, string value) id value
regexpConstValue.rel: delete
char_class_escape.rel: reorder charClassEscape.rel(int id, string value) id value
char_class_escape.rel: reorder charClassEscape.rel(@regexp_char_class_escape id, string value) id value
charClassEscape.rel: delete
named_backref.rel: reorder namedBackref.rel(int id, string name) id name
named_backref.rel: reorder namedBackref.rel(@regexp_backref id, string name) id name
namedBackref.rel: delete
unicode_property_escapename.rel: reorder unicodePropertyEscapeName.rel(int id, string name) id name
unicode_property_escapename.rel: reorder unicodePropertyEscapeName.rel(@regexp_unicode_property_escape id, string name) id name
unicodePropertyEscapeName.rel: delete
unicode_property_escapevalue.rel: reorder unicodePropertyEscapeValue.rel(int id, string value) id value
unicode_property_escapevalue.rel: reorder unicodePropertyEscapeValue.rel(@regexp_unicode_property_escape id, string value) id value
unicodePropertyEscapeValue.rel: delete
is_generator.rel: reorder isGenerator.rel(int fun) fun
is_generator.rel: reorder isGenerator.rel(@function fun) fun
isGenerator.rel: delete
has_rest_parameter.rel: reorder hasRestParameter.rel(int fun) fun
has_rest_parameter.rel: reorder hasRestParameter.rel(@function fun) fun
hasRestParameter.rel: delete
is_async.rel: reorder isAsync.rel(int fun) fun
is_async.rel: reorder isAsync.rel(@function fun) fun
isAsync.rel: delete

View File

@@ -1,3 +1,17 @@
## 1.0.1
No user-facing changes.
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
## 0.8.16
No user-facing changes.
## 0.8.15
### Minor Analysis Improvements

View File

@@ -36,7 +36,7 @@
<p>
For JavaScript in the browser,
<code>RandomSource.getRandomValues</code> provides a cryptographically
<code>crypto.getRandomValues</code> provides a cryptographically
secure pseudo-random number generator.
</p>
@@ -69,7 +69,7 @@
<references>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Pseudorandom_number_generator">Pseudo-random number generator</a>.</li>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues">RandomSource.getRandomValues</a>.</li>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues">Crypto: getRandomValues()</a>.</li>
<li>NodeJS: <a href="https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback">crypto.randomBytes</a></li>
</references>
</qhelp>

View File

@@ -2,5 +2,7 @@ function securePassword() {
// GOOD: the random suffix is cryptographically secure
var suffix = window.crypto.getRandomValues(new Uint32Array(1))[0];
var password = "myPassword" + suffix;
return password;
// GOOD: if a random value between 0 and 1 is desired
var secret = window.crypto.getRandomValues(new Uint32Array(1))[0] * Math.pow(2,-32);
}

View File

@@ -19,6 +19,10 @@
If possible, store configuration files including credential data separately from the source code,
in a secure location with restricted access.
</p>
<p>
If the credentials are a placeholder value, make sure the value is obviously a placeholder by
using a name such as <code>"SampleToken"</code> or <code>"MyPassword"</code>.
</p>
</recommendation>
<example>

View File

@@ -30,7 +30,7 @@ where
// exclude dummy passwords and templates
not (
sink.getNode().(Sink).(DefaultCredentialsSink).getKind() =
["password", "credentials", "token"] and
["password", "credentials", "token", "key"] and
PasswordHeuristics::isDummyPassword(val)
or
sink.getNode().(Sink).getKind() = "authorization header" and

View File

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

View File

@@ -0,0 +1,5 @@
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.

View File

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.8.15
lastReleaseVersion: 1.0.1

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly evaluating user input (for example, an HTTP request parameter) as code without properly
sanitizing the input first allows an attacker arbitrary code execution. This can occur when user
input is treated as JavaScript, or passed to a framework which interprets it as an expression to be
evaluated. Examples include AngularJS expressions or JQuery selectors.
</p>
</overview>
<recommendation>
<p>
Avoid including user input in any expression which may be dynamically evaluated. If user input must
be included, use context-specific escaping before
including it. It is important that the correct escaping is used for the type of evaluation that will
occur.
</p>
</recommendation>
<example>
<p>
The following example shows part of the page URL being evaluated as JavaScript code on the server. This allows an
attacker to provide JavaScript within the URL and send it to server. client side attacks need victim users interaction
like clicking on a attacker provided URL.
</p>
<sample src="examples/CodeInjection.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/Code_injection">Code Injection</a>.
</li>
<li>
PortSwigger Research Blog:
<a href="https://portswigger.net/research/server-side-template-injection">Server-Side Template Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,6 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="CodeInjection.inc.qhelp" />
</qhelp>

View File

@@ -0,0 +1,89 @@
/**
* @name Code injection
* @description Interpreting unsanitized user input as code allows a malicious user arbitrary
* code execution.
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id js/code-injection-dynamic-import
* @tags security
* external/cwe/cwe-094
* external/cwe/cwe-095
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import DataFlow
import DataFlow::PathGraph
abstract class Sanitizer extends DataFlow::Node { }
/** A non-first leaf in a string-concatenation. Seen as a sanitizer for dynamic import code injection. */
class NonFirstStringConcatLeaf extends Sanitizer {
NonFirstStringConcatLeaf() {
exists(StringOps::ConcatenationRoot root |
this = root.getALeaf() and
not this = root.getFirstLeaf()
)
or
exists(DataFlow::CallNode join |
join = DataFlow::moduleMember("path", "join").getACall() and
this = join.getArgument([1 .. join.getNumArgument() - 1])
)
}
}
/**
* The dynamic import expression input can be a `data:` URL which loads any module from that data
*/
class DynamicImport extends DataFlow::ExprNode {
DynamicImport() { this = any(DynamicImportExpr e).getSource().flow() }
}
/**
* The dynamic import expression input can be a `data:` URL which loads any module from that data
*/
class WorkerThreads extends DataFlow::Node {
WorkerThreads() {
this = API::moduleImport("node:worker_threads").getMember("Worker").getParameter(0).asSink()
}
}
class UrlConstructorLabel extends FlowLabel {
UrlConstructorLabel() { this = "UrlConstructorLabel" }
}
/**
* A taint-tracking configuration for reasoning about code injection vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CodeInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof DynamicImport }
override predicate isSink(DataFlow::Node sink, FlowLabel label) {
sink instanceof WorkerThreads and label instanceof UrlConstructorLabel
}
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
) {
exists(DataFlow::NewNode newUrl | succ = newUrl |
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
pred = newUrl.getArgument(0)
) and
predlbl instanceof StandardFlowLabel and
succlbl instanceof UrlConstructorLabel
}
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This command line depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,14 @@
const { Worker } = require('node:worker_threads');
var app = require('express')();
app.post('/path', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
const payloadURL = new URL(payload)
new Worker(payloadURL);
});
app.post('/path2', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
await import(payload)
});

View File

@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Controlling the value of arbitrary environment variables from user-controllable data is not safe.
</p>
</overview>
<recommendation>
<p>
Restrict this operation only to privileged users or only for some not important environment variables.
</p>
</recommendation>
<example>
<p>
The following example allows unauthorized users to assign a value to any environment variable.
</p>
<sample src="examples/Bad_Value_And_Key_Assignment.js" />
</example>
<references>
<li> <a href="https://huntr.com/bounties/00ec6847-125b-43e9-9658-d3cace1751d6/">Admin account TakeOver in mintplex-labs/anything-llm</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,69 @@
/**
* @name User controlled arbitrary environment variable injection
* @description creating arbitrary environment variables from user controlled data is not secure
* @kind path-problem
* @id js/env-key-and-value-injection
* @problem.severity error
* @security-severity 7.5
* @precision medium
* @tags security
* external/cwe/cwe-089
*/
import javascript
import DataFlow::PathGraph
/** A taint tracking configuration for unsafe environment injection. */
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "envInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink = keyOfEnv() or
sink = valueOfEnv()
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::InvokeNode ikn |
ikn = DataFlow::globalVarRef("Object").getAMemberInvocation("keys")
|
pred = ikn.getArgument(0) and
(
succ = ikn.getAChainedMethodCall(["filter", "map"]) or
succ = ikn or
succ = ikn.getAChainedMethodCall("forEach").getABoundCallbackParameter(0, 0)
)
)
}
}
DataFlow::Node keyOfEnv() {
result =
NodeJSLib::process().getAPropertyRead("env").getAPropertyWrite().getPropertyNameExpr().flow()
}
DataFlow::Node valueOfEnv() {
result = API::moduleImport("process").getMember("env").getAMember().asSink()
}
private predicate readToProcessEnv(DataFlow::Node envKey, DataFlow::Node envValue) {
exists(DataFlow::PropWrite env |
env = NodeJSLib::process().getAPropertyRead("env").getAPropertyWrite()
|
envKey = env.getPropertyNameExpr().flow() and
envValue = env.getRhs()
)
}
from
Configuration cfgForValue, Configuration cfgForKey, DataFlow::PathNode source,
DataFlow::PathNode envKey, DataFlow::PathNode envValue
where
cfgForValue.hasFlowPath(source, envKey) and
envKey.getNode() = keyOfEnv() and
cfgForKey.hasFlowPath(source, envValue) and
envValue.getNode() = valueOfEnv() and
readToProcessEnv(envKey.getNode(), envValue.getNode())
select envKey.getNode(), source, envKey, "arbitrary environment variable assignment from this $@.",
source.getNode(), "user controllable source"

View File

@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Assigning Value to environment variables from user-controllable data is not safe.
</p>
</overview>
<recommendation>
<p>
Restrict this operation only to privileged users or only for some not important environment variables.
</p>
</recommendation>
<example>
<p>
The following example allows unauthorized users to assign a value to a critical environment variable.
</p>
<sample src="examples/Bad_Value_Assignment.js" />
</example>
<references>
<li><a href="https://huntr.com/bounties/00ec6847-125b-43e9-9658-d3cace1751d6/">Admin account TakeOver in mintplex-labs/anything-llm</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,30 @@
/**
* @name User controlled environment variable value injection
* @description assigning important environment variables from user controlled data is not secure
* @kind path-problem
* @id js/env-value-injection
* @problem.severity error
* @security-severity 7.5
* @precision medium
* @tags security
* external/cwe/cwe-089
*/
import javascript
import DataFlow::PathGraph
/** A taint tracking configuration for unsafe environment injection. */
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "envInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink = API::moduleImport("process").getMember("env").getAMember().asSink()
}
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "this environment variable assignment is $@.",
source.getNode(), "user controllable"

View File

@@ -0,0 +1,8 @@
const http = require('node:http');
http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;
process.env[EnvKey] = EnvValue; // NOT OK
res.end('env has been injected!');
});

View File

@@ -0,0 +1,8 @@
const http = require('node:http');
http.createServer((req, res) => {
const { EnvValue } = req.body;
process.env["A_Critical_Env"] = EnvValue; // NOT OK
res.end('env has been injected!');
});

View File

@@ -0,0 +1,39 @@
const express = require('express')
const app = express()
const jwtJsonwebtoken = require('jsonwebtoken');
const jwt_decode = require('jwt-decode');
const jwt_simple = require('jwt-simple');
const jose = require('jose')
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jose', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jose.decodeJwt(UserToken)
})
app.get('/jwtDecode', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwt_decode(UserToken)
})
app.get('/jwtSimple', (req, res) => {
const UserToken = req.headers.authorization;
// jwt.decode(token, key, noVerify, algorithm)
// BAD: no signature verification
jwt_simple.decode(UserToken, getSecret(), true);
})
app.get('/jwtJsonwebtoken', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -0,0 +1,56 @@
const express = require('express')
const app = express()
const jwtJsonwebtoken = require('jsonwebtoken');
const jwt_decode = require('jwt-decode');
const jwt_simple = require('jwt-simple');
const jose = require('jose')
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jose1', async (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret()))
})
app.get('/jose2', async (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jose.decodeJwt(UserToken)
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret()))
})
app.get('/jwtSimple1', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwt_simple.decode(UserToken, getSecret(), false);
jwt_simple.decode(UserToken, getSecret());
})
app.get('/jwtSimple2', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
jwt_simple.decode(UserToken, getSecret(), true);
jwt_simple.decode(UserToken, getSecret());
})
app.get('/jwtJsonwebtoken1', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
jwtJsonwebtoken.verify(UserToken, getSecret())
})
app.get('/jwtJsonwebtoken2', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken)
jwtJsonwebtoken.verify(UserToken, getSecret())
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -0,0 +1,54 @@
import javascript
DataFlow::Node unverifiedDecode() {
result = API::moduleImport("jsonwebtoken").getMember("decode").getParameter(0).asSink()
or
exists(API::Node verify | verify = API::moduleImport("jsonwebtoken").getMember("verify") |
verify
.getParameter(2)
.getMember("algorithms")
.getUnknownMember()
.asSink()
.mayHaveStringValue("none") and
result = verify.getParameter(0).asSink()
)
or
// jwt-simple
exists(API::Node n | n = API::moduleImport("jwt-simple").getMember("decode") |
n.getParameter(2).asSink().asExpr() = any(BoolLiteral b | b.getBoolValue() = true) and
result = n.getParameter(0).asSink()
)
or
// jwt-decode
result = API::moduleImport("jwt-decode").getParameter(0).asSink()
or
//jose
result = API::moduleImport("jose").getMember("decodeJwt").getParameter(0).asSink()
}
DataFlow::Node verifiedDecode() {
exists(API::Node verify | verify = API::moduleImport("jsonwebtoken").getMember("verify") |
(
not verify
.getParameter(2)
.getMember("algorithms")
.getUnknownMember()
.asSink()
.mayHaveStringValue("none") or
not exists(verify.getParameter(2).getMember("algorithms"))
) and
result = verify.getParameter(0).asSink()
)
or
// jwt-simple
exists(API::Node n | n = API::moduleImport("jwt-simple").getMember("decode") |
(
n.getParameter(2).asSink().asExpr() = any(BoolLiteral b | b.getBoolValue() = false) or
not exists(n.getParameter(2))
) and
result = n.getParameter(0).asSink()
or
//jose
result = API::moduleImport("jose").getMember("jwtVerify").getParameter(0).asSink()
)
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
</p>
<p>
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
</p>
</overview>
<recommendation>
<p>
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
</p>
</recommendation>
<example>
<p>
In the following code, you can see the proper usage of the most popular JWT libraries.
</p>
<sample src="Examples/Good.js" />
<p>
In the following code, you can see the improper usage of the most popular JWT libraries.
</p>
<sample src="Examples/Bad.js" />
</example>
<references>
<li>
<a href="https://www.ghostccamm.com/blog/multi_strapi_vulns/#cve-2023-22893-authentication-bypass-for-aws-cognito-login-provider-in-strapi-versions-456">JWT claim has not been verified</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,40 @@
/**
* @name JWT missing secret or public key verification
* @description The application does not verify the JWT payload with a cryptographic secret or public key.
* @kind path-problem
* @problem.severity error
* @security-severity 8.0
* @precision high
* @id js/decode-jwt-without-verification
* @tags security
* external/cwe/cwe-347
*/
import javascript
import DataFlow::PathGraph
import JWT
class ConfigurationUnverifiedDecode extends TaintTracking::Configuration {
ConfigurationUnverifiedDecode() { this = "jsonwebtoken without any signature verification" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink = unverifiedDecode() }
}
class ConfigurationVerifiedDecode extends TaintTracking::Configuration {
ConfigurationVerifiedDecode() { this = "jsonwebtoken with signature verification" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink = verifiedDecode() }
}
from ConfigurationUnverifiedDecode cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink) and
not exists(ConfigurationVerifiedDecode cfg2 |
cfg2.hasFlowPath(any(DataFlow::PathNode p | p.getNode() = source.getNode()), _)
)
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
"without signature verification"

View File

@@ -0,0 +1,55 @@
/**
* @name JWT missing secret or public key verification
* @description The application does not verify the JWT payload with a cryptographic secret or public key.
* @kind path-problem
* @problem.severity error
* @security-severity 8.0
* @precision high
* @id js/decode-jwt-without-verification-local-source
* @tags security
* external/cwe/cwe-347
*/
import javascript
import DataFlow::PathGraph
import JWT
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "jsonwebtoken without any signature verification" }
override predicate isSource(DataFlow::Node source) {
source = [unverifiedDecode(), verifiedDecode()].getALocalSource()
}
override predicate isSink(DataFlow::Node sink) {
sink = unverifiedDecode()
or
sink = verifiedDecode()
}
}
/** Holds if `source` flows to the first parameter of jsonwebtoken.verify */
predicate isSafe(Configuration cfg, DataFlow::Node source) {
exists(DataFlow::Node sink |
cfg.hasFlow(source, sink) and
sink = verifiedDecode()
)
}
/**
* Holds if:
* - `source` does not flow to the first parameter of `jsonwebtoken.verify`, and
* - `source` flows to the first parameter of `jsonwebtoken.decode`
*/
predicate isVulnerable(Configuration cfg, DataFlow::Node source, DataFlow::Node sink) {
not isSafe(cfg, source) and // i.e., source does not flow to a verify call
cfg.hasFlow(source, sink) and // but it does flow to something else
sink = unverifiedDecode() // and that something else is a call to decode.
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink) and
isVulnerable(cfg, source.getNode(), sink.getNode())
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
"without signature verification"

View File

@@ -0,0 +1,43 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting Compressed files with any compression algorithm like gzip can cause to denial of service attacks.</p>
<p>Attackers can compress a huge file which created by repeated similiar byte and convert it to a small compressed file.</p>
</overview>
<recommendation>
<p>When you want to decompress a user-provided compressed file you must be careful about the decompression ratio or read these files within a loop byte by byte to be able to manage the decompressed size in each cycle of the loop.</p>
</recommendation>
<example>
<p>
JsZip: check uncompressedSize Object Field before extraction.
</p>
<sample src="jszip_good.js"/>
<p>
nodejs Zlib: use <a href="https://nodejs.org/dist/latest-v18.x/docs/api/zlib.html#class-options">maxOutputLength option</a> which it'll limit the buffer read size
</p>
<sample src="zlib_good.js" />
<p>
node-tar: use <a href="https://github.com/isaacs/node-tar/blob/8c5af15e43a769fd24aa7f1c84d93e54824d19d2/lib/list.js#L90">maxReadSize option</a> which it'll limit the buffer read size
</p>
<sample src="node-tar_good.js" />
</example>
<references>
<li>
<a href="https://github.com/advisories/GHSA-8225-6cvr-8pqp">CVE-2017-16129</a>
</li>
<li>
<a href="https://www.bamsoftware.com/hacks/zipbomb/">A great research to gain more impact by this kind of attacks</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,35 @@
/**
* @name User-controlled file decompression
* @description User-controlled data that flows into decompression library APIs without checking the compression rate is dangerous
* @kind path-problem
* @problem.severity error
* @security-severity 7.8
* @precision high
* @id js/user-controlled-data-decompression
* @tags security
* experimental
* external/cwe/cwe-522
*/
import javascript
import DataFlow::PathGraph
import DecompressionBombs
class BombConfiguration extends TaintTracking::Configuration {
BombConfiguration() { this = "DecompressionBombs" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionBomb::Sink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DecompressionBomb::AdditionalTaintStep addstep |
addstep.isAdditionalTaintStep(pred, succ)
)
}
}
from BombConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This Decompression depends on a $@.", source.getNode(),
"potentially untrusted source"

View File

@@ -0,0 +1,432 @@
import javascript
import experimental.semmle.javascript.FormParsers
import experimental.semmle.javascript.ReadableStream
import DataFlow::PathGraph
module DecompressionBomb {
/**
* The Sinks of uncontrolled data decompression
*/
class Sink extends DataFlow::Node {
Sink() { this = any(Range r).sink() }
}
/**
* The additional taint steps that need for creating taint tracking or dataflow.
*/
abstract class AdditionalTaintStep extends string {
AdditionalTaintStep() { this = "AdditionalTaintStep" }
/**
* Holds if there is a additional taint step between pred and succ.
*/
abstract predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ);
}
/**
* A abstract class responsible for extending new decompression sinks
*/
abstract class Range extends API::Node {
/**
* Gets the sink of responsible for decompression node
*
* it can be a path, stream of compressed data,
* or a call to function that use pipe
*/
abstract DataFlow::Node sink();
}
}
/**
* Provides additional taint steps for Readable Stream object
*/
module ReadableStream {
class ReadableStreamAdditionalTaintStep extends DecompressionBomb::AdditionalTaintStep {
ReadableStreamAdditionalTaintStep() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
(
readablePipeAdditionalTaintStep(pred, succ)
or
streamPipelineAdditionalTaintStep(pred, succ)
or
promisesFileHandlePipeAdditionalTaintStep(pred, succ)
)
}
}
}
/**
* Provides additional taint steps for File system access functions
*/
module FileSystemAccessAdditionalTaintStep {
class ReadableStreamAdditionalTaintStep extends DecompressionBomb::AdditionalTaintStep {
ReadableStreamAdditionalTaintStep() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// additional taint step for fs.readFile(pred)
// It can be global additional step too
exists(DataFlow::CallNode n | n = DataFlow::moduleMember("fs", "readFile").getACall() |
pred = n.getArgument(0) and succ = n.getABoundCallbackParameter(1, 1)
)
or
exists(FileSystemReadAccess cn |
pred = cn.getAPathArgument() and
succ = cn.getADataNode()
)
}
}
}
/**
* Provides Models for [jszip](https://www.npmjs.com/package/jszip) package
*/
module JsZip {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("jszip").getMember("loadAsync") }
override DataFlow::Node sink() {
result = this.getParameter(0).asSink() and not this.sanitizer(this)
}
/**
* Gets a jszip `loadAsync` instance
* and Holds if member of name `uncompressedSize` exists
*/
predicate sanitizer(API::Node loadAsync) {
exists(loadAsync.getASuccessor*().getMember("_data").getMember("uncompressedSize"))
}
}
}
/**
* Provides Models for [node-tar](https://www.npmjs.com/package/tar) package
*/
module NodeTar {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("tar").getMember(["x", "extract"]) }
override DataFlow::Node sink() {
(
// piping tar.x()
result = this.getACall()
or
// tar.x({file: filename})
result = this.getParameter(0).getMember("file").asSink()
) and
// and there shouldn't be a "maxReadSize: ANum" option
not this.sanitizer(this.getParameter(0))
}
/**
* Gets a options parameter that belong to a `tar` instance
* and Holds if "maxReadSize: ANumber" option exists
*/
predicate sanitizer(API::Node tarExtract) { exists(tarExtract.getMember("maxReadSize")) }
}
/**
* The decompression Additional Taint Steps
*/
class DecompressionAdditionalSteps extends DecompressionBomb::AdditionalTaintStep {
DecompressionAdditionalSteps() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node n | n = API::moduleImport("tar") |
pred = n.asSource() and
(
succ = n.getMember("x").getACall() or
succ = n.getMember("x").getACall().getArgument(0)
)
)
}
}
}
/**
* Provides Models for `node:zlib` package
*/
module Zlib {
/**
* The decompression sinks of `node:zlib`
*/
class DecompressionBomb extends DecompressionBomb::Range {
boolean isSynk;
DecompressionBomb() {
this =
API::moduleImport("zlib")
.getMember([
"gunzip", "gunzipSync", "unzip", "unzipSync", "brotliDecompress",
"brotliDecompressSync", "inflateSync", "inflateRawSync", "inflate", "inflateRaw"
]) and
isSynk = true
or
this =
API::moduleImport("zlib")
.getMember([
"createGunzip", "createBrotliDecompress", "createUnzip", "createInflate",
"createInflateRaw"
]) and
isSynk = false
}
override DataFlow::Node sink() {
result = this.getACall() and
not this.sanitizer(this.getParameter(0)) and
isSynk = false
or
result = this.getACall().getArgument(0) and
not this.sanitizer(this.getParameter(1)) and
isSynk = true
}
/**
* Gets a options parameter that belong to a zlib instance
* and Holds if "maxOutputLength: ANumber" option exists
*/
predicate sanitizer(API::Node zlib) { exists(zlib.getMember("maxOutputLength")) }
}
}
/**
* Provides Models for [pako](https://www.npmjs.com/package/pako) package
*/
module Pako {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() {
this = API::moduleImport("pako").getMember(["inflate", "inflateRaw", "ungzip"])
}
override DataFlow::Node sink() { result = this.getParameter(0).asSink() }
}
/**
* The decompression Additional Taint Steps
*/
class DecompressionAdditionalSteps extends DecompressionBomb::AdditionalTaintStep {
DecompressionAdditionalSteps() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// succ = new Uint8Array(pred)
exists(DataFlow::Node n, NewExpr ne | ne = n.asExpr() |
pred.asExpr() = ne.getArgument(0) and
succ.asExpr() = ne and
ne.getCalleeName() = "Uint8Array"
)
}
}
}
/**
* Provides Models for [adm-zip](https://www.npmjs.com/package/adm-zip) package
*/
module AdmZip {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("adm-zip").getInstance() }
override DataFlow::Node sink() {
result =
this.getMember(["extractAllTo", "extractEntryTo", "readAsText"]).getReturn().asSource()
or
result = this.getASuccessor*().getMember("getData").getReturn().asSource()
}
}
/**
* The decompression Additional Taint Steps
*/
class DecompressionAdditionalSteps extends DecompressionBomb::AdditionalTaintStep {
DecompressionAdditionalSteps() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node n | n = API::moduleImport("adm-zip") |
pred = n.getParameter(0).asSink() and
(
succ =
n.getInstance()
.getMember(["extractAllTo", "extractEntryTo", "readAsText"])
.getReturn()
.asSource()
or
succ =
n.getInstance()
.getMember("getEntries")
.getASuccessor*()
.getMember("getData")
.getReturn()
.asSource()
)
)
}
}
}
/**
* Provides Models for [decompress](https://www.npmjs.com/package/decompress) package
*/
module Decompress {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("decompress") }
override DataFlow::Node sink() { result = this.getACall().getArgument(0) }
}
}
/**
* Provides Models for [gunzip-maybe][https://www.npmjs.com/package/gunzip-maybe] package
*/
module GunzipMaybe {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("gunzip-maybe") }
override DataFlow::Node sink() { result = this.getACall() }
}
}
/**
* Provides Models for [unbzip2-stream](https://www.npmjs.com/package/unbzip2-stream) package
*/
module Unbzip2Stream {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
DecompressionBomb() { this = API::moduleImport("unbzip2-stream") }
override DataFlow::Node sink() { result = this.getACall() }
}
}
/**
* Provides Models for [unzipper](https://www.npmjs.com/package/unzipper) package
*/
module Unzipper {
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
string funcName;
DecompressionBomb() {
this = API::moduleImport("unzipper").getMember(["Extract", "Parse", "ParseOne"]) and
funcName = ["Extract", "Parse", "ParseOne"]
or
this = API::moduleImport("unzipper").getMember("Open") and
// open has some functions which will be specified in sink predicate
funcName = "Open"
}
override DataFlow::Node sink() {
result = this.getMember(["buffer", "file", "url", "file"]).getACall().getArgument(0) and
funcName = "Open"
or
result = this.getACall() and
funcName = ["Extract", "Parse", "ParseOne"]
}
/**
* Gets a
* and Holds if unzipper instance has a member `uncompressedSize`
*
* it is really difficult to implement this sanitizer,
* so i'm going to check if there is a member like `vars.uncompressedSize` in whole DB or not!
*/
predicate sanitizer() {
exists(this.getASuccessor*().getMember("vars").getMember("uncompressedSize")) and
funcName = ["Extract", "Parse", "ParseOne"]
}
}
}
/**
* Provides Models for [yauzl](https://www.npmjs.com/package/yauzl) package
*/
module Yauzl {
API::Node test() { result = API::moduleImport("yauzl").getASuccessor*() }
/**
* The decompression bomb sinks
*/
class DecompressionBomb extends DecompressionBomb::Range {
// open function has a sanitizer
string methodName;
DecompressionBomb() {
this =
API::moduleImport("yauzl").getMember(["fromFd", "fromBuffer", "fromRandomAccessReader"]) and
methodName = "from"
or
this = API::moduleImport("yauzl").getMember("open") and
methodName = "open"
}
override DataFlow::Node sink() {
(
result = this.getParameter(2).getParameter(1).getMember("readEntry").getACall() or
result =
this.getParameter(2)
.getParameter(1)
.getMember("openReadStream")
.getParameter(1)
.getParameter(1)
.asSource()
) and
not this.sanitizer() and
methodName = "open"
or
result = this.getParameter(0).asSink() and
methodName = "from"
}
/**
* Gets a
* and Holds if yauzl `open` instance has a member `uncompressedSize`
*/
predicate sanitizer() {
exists(this.getASuccessor*().getMember("uncompressedSize")) and
methodName = ["readStream", "open"]
}
}
/**
* The decompression Additional Taint Steps
*/
class DecompressionAdditionalSteps extends DecompressionBomb::AdditionalTaintStep {
DecompressionAdditionalSteps() { this = "AdditionalTaintStep" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node open | open = API::moduleImport("yauzl").getMember("open") |
pred = open.getParameter(0).asSink() and
(
succ = open.getParameter(2).getParameter(1).getMember("readEntry").getACall() or
succ =
open.getParameter(2)
.getParameter(1)
.getMember("openReadStream")
.getParameter(1)
.getParameter(1)
.asSource()
)
)
}
}
}

View File

@@ -0,0 +1,11 @@
const jszipp = require("jszip");
function zipBombSafe(zipFile) {
jszipp.loadAsync(zipFile.data).then(function (zip) {
if (zip.file("10GB")["_data"]["uncompressedSize"] > 1024 * 1024 * 8) {
console.log("error")
}
zip.file("10GB").async("uint8array").then(function (u8) {
console.log(u8);
});
});
}

View File

@@ -0,0 +1,8 @@
const tar = require("tar");
tar.x({
file: tarFileName,
strip: 1,
C: 'some-dir',
maxReadSize: 16 * 1024 * 1024 // 16 MB
})

View File

@@ -0,0 +1,11 @@
const zlib = require("zlib");
zlib.gunzip(
inputZipFile.data,
{ maxOutputLength: 1024 * 1024 * 5 },
(err, buffer) => {
doSomeThingWithData(buffer);
});
zlib.gunzipSync(inputZipFile.data, { maxOutputLength: 1024 * 1024 * 5 });
inputZipFile.pipe(zlib.createGunzip({ maxOutputLength: 1024 * 1024 * 5 })).pipe(outputFile);

View File

@@ -0,0 +1,211 @@
/**
* Models the `execa` library in terms of `FileSystemAccess` and `SystemCommandExecution`.
*/
import javascript
/**
* Provide model for [Execa](https://github.com/sindresorhus/execa) package
*/
module Execa {
/**
* The Execa input file read and output file write
*/
class ExecaFileSystemAccess extends FileSystemReadAccess, DataFlow::Node {
API::Node execaArg;
boolean isPipedToFile;
ExecaFileSystemAccess() {
(
execaArg = API::moduleImport("execa").getMember("$").getParameter(0) and
isPipedToFile = false
or
execaArg =
API::moduleImport("execa")
.getMember(["execa", "execaCommand", "execaCommandSync", "execaSync"])
.getParameter([0, 1, 2]) and
isPipedToFile = false
or
execaArg =
API::moduleImport("execa")
.getMember(["execa", "execaCommand", "execaCommandSync", "execaSync"])
.getReturn()
.getMember(["pipeStdout", "pipeAll", "pipeStderr"])
.getParameter(0) and
isPipedToFile = true
) and
this = execaArg.asSink()
}
override DataFlow::Node getADataNode() { none() }
override DataFlow::Node getAPathArgument() {
result = execaArg.getMember("inputFile").asSink() and isPipedToFile = false
or
result = execaArg.asSink() and isPipedToFile = true
}
}
/**
* A call to `execa.execa` or `execa.execaSync`
*/
class ExecaCall extends API::CallNode {
boolean isSync;
ExecaCall() {
this = API::moduleImport("execa").getMember("execa").getACall() and
isSync = false
or
this = API::moduleImport("execa").getMember("execaSync").getACall() and
isSync = true
}
}
/**
* The system command execution nodes for `execa.execa` or `execa.execaSync` functions
*/
class ExecaExec extends SystemCommandExecution, ExecaCall {
ExecaExec() { isSync = [false, true] }
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// if shell: true then first and second args are sinks
// options can be third argument
arg = [this.getArgument(0), this.getParameter(1).getUnknownMember().asSink()] and
isExecaShellEnable(this.getParameter(2))
or
// options can be second argument
arg = this.getArgument(0) and
isExecaShellEnable(this.getParameter(1))
}
override DataFlow::Node getArgumentList() {
// execa(cmd, [arg]);
exists(DataFlow::Node arg | arg = this.getArgument(1) |
// if it is a object then it is a option argument not command argument
result = arg and not arg.asExpr() instanceof ObjectExpr
)
}
override predicate isSync() { isSync = true }
override DataFlow::Node getOptionsArg() {
result = this.getLastArgument() and result.asExpr() instanceof ObjectExpr
}
}
/**
* A call to `execa.$` or `execa.$.sync` or `execa.$({})` or `execa.$.sync({})` tag functions
*/
private class ExecaScriptCall extends API::CallNode {
boolean isSync;
ExecaScriptCall() {
exists(API::Node script |
script =
[
API::moduleImport("execa").getMember("$"),
API::moduleImport("execa").getMember("$").getReturn()
]
|
this = script.getACall() and
isSync = false
or
this = script.getMember("sync").getACall() and
isSync = true
)
}
}
/**
* The system command execution nodes for `execa.$` or `execa.$.sync` tag functions
*/
class ExecaScript extends SystemCommandExecution, ExecaScriptCall {
ExecaScript() { isSync = [false, true] }
override DataFlow::Node getACommandArgument() {
result = this.getParameter(1).asSink() and
not isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
}
override predicate isShellInterpreted(DataFlow::Node arg) {
isExecaShellEnable(this.getParameter(0)) and
arg = this.getAParameter().asSink()
}
override DataFlow::Node getArgumentList() {
result = this.getParameter(any(int i | i >= 1)).asSink() and
isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
or
result = this.getParameter(any(int i | i >= 2)).asSink() and
not isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
}
override DataFlow::Node getOptionsArg() { result = this.getParameter(0).asSink() }
override predicate isSync() { isSync = true }
}
/**
* A call to `execa.execaCommandSync` or `execa.execaCommand`
*/
private class ExecaCommandCall extends API::CallNode {
boolean isSync;
ExecaCommandCall() {
this = API::moduleImport("execa").getMember("execaCommandSync").getACall() and
isSync = true
or
this = API::moduleImport("execa").getMember("execaCommand").getACall() and
isSync = false
}
}
/**
* The system command execution nodes for `execa.execaCommand` or `execa.execaCommandSync` functions
*/
class ExecaCommandExec extends SystemCommandExecution, ExecaCommandCall {
ExecaCommandExec() { isSync = [false, true] }
override DataFlow::Node getACommandArgument() {
result = this.(DataFlow::CallNode).getArgument(0)
}
override DataFlow::Node getArgumentList() {
// execaCommand(`${cmd} ${arg}`);
result.asExpr() = this.getParameter(0).asSink().asExpr().getAChildExpr() and
not result.asExpr() = this.getArgument(0).asExpr().getChildExpr(0)
}
override predicate isShellInterpreted(DataFlow::Node arg) {
// execaCommandSync(`${cmd} ${arg}`, {shell: true})
arg.asExpr() = this.getArgument(0).asExpr().getAChildExpr+() and
isExecaShellEnable(this.getParameter(1))
or
// there is only one argument that is constructed in previous nodes,
// it makes sanitizing really hard to select whether it is vulnerable to argument injection or not
arg = this.getParameter(0).asSink() and
not exists(this.getArgument(0).asExpr().getChildExpr(1))
}
override predicate isSync() { isSync = true }
override DataFlow::Node getOptionsArg() {
result = this.getLastArgument() and result.asExpr() instanceof ObjectExpr
}
}
/** Gets a TemplateLiteral and check if first child is a template element */
private predicate isTaggedTemplateFirstChildAnElement(TemplateLiteral templateLit) {
exists(templateLit.getChildExpr(0).(TemplateElement))
}
/**
* Holds whether Execa has shell enabled options or not, get Parameter responsible for options
*/
pragma[inline]
private predicate isExecaShellEnable(API::Node n) {
n.getMember("shell").asSink().asExpr().(BooleanLiteral).getValue() = "true"
}
}

View File

@@ -0,0 +1,179 @@
/**
* Provides classes for modeling the server-side form/file parsing libraries.
*/
import javascript
import experimental.semmle.javascript.ReadableStream
/**
* A module for modeling [busboy](https://www.npmjs.com/package/busboy) package
*/
module BusBoy {
/**
* A source of remote flow from the `Busboy` library.
*/
private class BusBoyRemoteFlow extends RemoteFlowSource {
BusBoyRemoteFlow() {
exists(API::Node busboyOnEvent |
busboyOnEvent = API::moduleImport("busboy").getReturn().getMember("on")
|
// Files
busboyOnEvent.getParameter(0).asSink().mayHaveStringValue("file") and
// second param of 'file' event is a Readable stream
this = readableStreamDataNode(busboyOnEvent.getParameter(1).getParameter(1))
or
// Fields
busboyOnEvent.getParameter(0).asSink().mayHaveStringValue(["file", "field"]) and
this =
API::moduleImport("busboy")
.getReturn()
.getMember("on")
.getParameter(1)
.getAParameter()
.asSource()
)
}
override string getSourceType() { result = "parsed user value from Busbuy" }
}
/**
* A busboy file data step according to a Readable Stream type
*/
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node busboyOnEvent |
busboyOnEvent = API::moduleImport("busboy").getReturn().getMember("on")
|
busboyOnEvent.getParameter(0).asSink().mayHaveStringValue("file") and
customStreamPipeAdditionalTaintStep(busboyOnEvent.getParameter(1).getParameter(1), pred,
succ)
)
}
}
}
/**
* A module for modeling [formidable](https://www.npmjs.com/package/formidable) package
*/
module Formidable {
/**
* A source of remote flow from the `Formidable` library parsing a HTTP request.
*/
private class FormidableRemoteFlow extends RemoteFlowSource {
FormidableRemoteFlow() {
exists(API::Node formidable |
formidable = API::moduleImport("formidable").getReturn()
or
formidable = API::moduleImport("formidable").getMember("formidable").getReturn()
or
formidable =
API::moduleImport("formidable").getMember(["IncomingForm", "Formidable"]).getInstance()
|
this =
formidable.getMember("parse").getACall().getABoundCallbackParameter(1, any(int i | i > 0))
or
// if callback is not provide a promise will be returned,
// return values contains [fields,files] members
exists(API::Node parseMethod |
parseMethod = formidable.getMember("parse") and parseMethod.getNumParameter() = 1
|
this = parseMethod.getReturn().asSource()
)
or
// event handler
this = formidable.getMember("on").getParameter(1).getAParameter().asSource()
)
}
override string getSourceType() { result = "parsed user value from Formidable" }
}
}
/**
* A module for modeling [multiparty](https://www.npmjs.com/package/multiparty) package
*/
module Multiparty {
/**
* A source of remote flow from the `Multiparty` library.
*/
private class MultipartyRemoteFlow extends RemoteFlowSource {
MultipartyRemoteFlow() {
exists(API::Node form |
form = API::moduleImport("multiparty").getMember("Form").getInstance()
|
exists(API::CallNode parse | parse = form.getMember("parse").getACall() |
this = parse.getParameter(1).getParameter([1, 2]).asSource()
)
or
exists(API::Node on | on = form.getMember("on") |
(
on.getParameter(0).asSink().mayHaveStringValue(["file", "field"]) and
this = on.getParameter(1).getParameter([0, 1]).asSource()
or
on.getParameter(0).asSink().mayHaveStringValue("part") and
this = readableStreamDataNode(on.getParameter(1).getParameter(0))
)
)
)
}
override string getSourceType() { result = "parsed user value from Multiparty" }
}
/**
* A multiparty part data step according to a Readable Stream type
*/
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node multipartyOnEvent |
multipartyOnEvent =
API::moduleImport("multiparty").getMember("Form").getInstance().getMember("on")
|
multipartyOnEvent.getParameter(0).asSink().mayHaveStringValue("part") and
customStreamPipeAdditionalTaintStep(multipartyOnEvent.getParameter(1).getParameter(0), pred,
succ)
)
}
}
}
/**
* A module for modeling [dicer](https://www.npmjs.com/package/dicer) package
*/
module Dicer {
/**
* A source of remote flow from the `dicer` library.
*/
private class DicerRemoteFlow extends RemoteFlowSource {
DicerRemoteFlow() {
exists(API::Node dicer | dicer = API::moduleImport("dicer").getInstance() |
exists(API::Node on | on = dicer.getMember("on") |
on.getParameter(0).asSink().mayHaveStringValue("part") and
this = readableStreamDataNode(on.getParameter(1).getParameter(0))
or
exists(API::Node onPart | onPart = on.getParameter(1).getParameter(0).getMember("on") |
onPart.getParameter(0).asSink().mayHaveStringValue("header") and
this = onPart.getParameter(1).getParameter(0).asSource()
)
)
)
}
override string getSourceType() { result = "parsed user value from Dicer" }
}
/**
* A dicer part data step according to a Readable Stream type
*/
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node onEvent |
onEvent = API::moduleImport("dicer").getInstance().getMember("on")
|
onEvent.getParameter(0).asSink().mayHaveStringValue("part") and
customStreamPipeAdditionalTaintStep(onEvent.getParameter(1).getParameter(0), pred, succ)
)
}
}
}

View File

@@ -0,0 +1,147 @@
/**
* Provides helper predicates to work with any Readable Stream in dataflow queries
*
* main predicate in which you can use by passing a Readable Stream is `customStreamPipeAdditionalTaintStep`
*/
import javascript
/**
* Holds if there is a step between `fs.createReadStream` and `stream.Readable.from` first parameters to all other piped parameters
*
* It can be global additional step too
*/
predicate readablePipeAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node receiver |
receiver =
[
API::moduleImport("fs").getMember("createReadStream"),
API::moduleImport("stream").getMember("Readable").getMember("from")
]
|
customStreamPipeAdditionalTaintStep(receiver, pred, succ)
or
pred = receiver.getParameter(0).asSink() and
succ = receiver.getReturn().asSource()
)
}
/**
* additional taint steps for piped stream from `createReadStream` method of `fs/promises.open`
*
* It can be global additional step too
*/
predicate promisesFileHandlePipeAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node receiver | receiver = nodeJsPromisesFileSystem().getMember("open") |
customStreamPipeAdditionalTaintStep(receiver, pred, succ)
or
pred = receiver.getParameter(0).asSink() and
succ = receiver.getReturn().asSource()
)
}
/**
* Gets nodejs `fs` Promises API
*/
API::Node nodeJsPromisesFileSystem() {
result = [API::moduleImport("fs").getMember("promises"), API::moduleImport("fs/promises")]
}
/**
* Holds if
* or `receiver.pipe(pred).pipe(sth).pipe(succ)`
*
* or `receiver.pipe(sth).pipe(pred).pipe(succ)`
*
* or `receiver.pipe(succ)` and receiver is pred
*
* Receiver is a Readable Stream object
*/
predicate customStreamPipeAdditionalTaintStep(
API::Node receiver, DataFlow::Node pred, DataFlow::Node succ
) {
// following connect the first pipe parameter to the last pipe parameter
exists(API::Node firstPipe | firstPipe = receiver.getMember("pipe") |
pred = firstPipe.getParameter(0).asSink() and
succ = firstPipe.getASuccessor*().getMember("pipe").getParameter(0).asSink()
)
or
// following connect a pipe parameter to the next pipe parameter
exists(API::Node cn | cn = receiver.getASuccessor+() |
pred = cn.getParameter(0).asSink() and
succ = cn.getReturn().getMember("pipe").getParameter(0).asSink()
)
or
// it is a function that its return value is a Readable stream object
pred = receiver.getReturn().asSource() and
succ = receiver.getReturn().getMember("pipe").getParameter(0).asSink()
or
// it is a Readable stream object
pred = receiver.asSource() and
succ = receiver.getMember("pipe").getParameter(0).asSink()
}
/**
* Holds if
*
* ```js
* await pipeline(
* pred,
* succ_or_pred,
* succ
* )
* ```
*
* It can be global additional step too
*/
predicate streamPipelineAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// this step connect the a pipeline parameter to the next pipeline parameter
exists(API::CallNode cn, int i |
// we assume that there are maximum 10 pipes mostly or maybe less
i in [0 .. 10] and
cn = nodeJsStream().getMember("pipeline").getACall()
|
pred = cn.getParameter(i).asSink() and
succ = cn.getParameter(i + 1).asSink()
)
or
// this step connect the first pipeline parameter to the next parameters
exists(API::CallNode cn, int i |
// we assume that there are maximum 10 pipes mostly or maybe less
i in [1 .. 10] and
cn = nodeJsStream().getMember("pipeline").getACall()
|
pred = cn.getParameter(0).asSink() and
succ = cn.getParameter(i).asSink()
)
}
/**
* Gets `stream` Promises API
*/
API::Node nodeJsStream() {
result = [API::moduleImport("stream/promises"), API::moduleImport("stream").getMember("promises")]
}
/**
* Gets a Readable stream object,
* and returns all nodes responsible for a data read of this Readable stream
*/
DataFlow::Node readableStreamDataNode(API::Node stream) {
result = stream.asSource()
or
// 'data' event
exists(API::CallNode onEvent | onEvent = stream.getMember("on").getACall() |
result = onEvent.getParameter(1).getParameter(0).asSource() and
onEvent.getParameter(0).asSink().mayHaveStringValue("data")
)
or
// 'Readable' event
exists(API::CallNode onEvent | onEvent = stream.getMember("on").getACall() |
(
result = onEvent.getParameter(1).getReceiver().getMember("read").getReturn().asSource() or
result = stream.getMember("read").getReturn().asSource()
) and
onEvent.getParameter(0).asSink().mayHaveStringValue("readable")
)
}

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 0.8.16-dev
version: 1.0.2-dev
groups:
- javascript
- queries

View File

@@ -0,0 +1,22 @@
passingPositiveTests
| PASSED | CommandInjection | tests.js:11:46:11:70 | // test ... jection |
| PASSED | CommandInjection | tests.js:12:43:12:67 | // test ... jection |
| PASSED | CommandInjection | tests.js:13:63:13:87 | // test ... jection |
| PASSED | CommandInjection | tests.js:14:62:14:86 | // test ... jection |
| PASSED | CommandInjection | tests.js:15:60:15:84 | // test ... jection |
| PASSED | CommandInjection | tests.js:17:45:17:69 | // test ... jection |
| PASSED | CommandInjection | tests.js:18:42:18:66 | // test ... jection |
| PASSED | CommandInjection | tests.js:19:62:19:86 | // test ... jection |
| PASSED | CommandInjection | tests.js:20:63:20:87 | // test ... jection |
| PASSED | CommandInjection | tests.js:21:60:21:84 | // test ... jection |
| PASSED | CommandInjection | tests.js:23:43:23:67 | // test ... jection |
| PASSED | CommandInjection | tests.js:24:40:24:64 | // test ... jection |
| PASSED | CommandInjection | tests.js:25:40:25:64 | // test ... jection |
| PASSED | CommandInjection | tests.js:26:60:26:84 | // test ... jection |
| PASSED | CommandInjection | tests.js:28:41:28:65 | // test ... jection |
| PASSED | CommandInjection | tests.js:29:58:29:82 | // test ... jection |
| PASSED | CommandInjection | tests.js:31:51:31:75 | // test ... jection |
| PASSED | CommandInjection | tests.js:32:68:32:92 | // test ... jection |
| PASSED | CommandInjection | tests.js:34:49:34:73 | // test ... jection |
| PASSED | CommandInjection | tests.js:35:66:35:90 | // test ... jection |
failingPositiveTests

View File

@@ -0,0 +1,36 @@
import { execa, execaSync, execaCommand, execaCommandSync, $ } from 'execa';
import http from 'node:http'
import url from 'url'
http.createServer(async function (req, res) {
let cmd = url.parse(req.url, true).query["cmd"][0];
let arg1 = url.parse(req.url, true).query["arg1"];
let arg2 = url.parse(req.url, true).query["arg2"];
let arg3 = url.parse(req.url, true).query["arg3"];
await $`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
await $`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
$({ shell: false }).sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
$({ shell: true }).sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
$({ shell: false }).sync`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
$.sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
$.sync`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
await $({ shell: true })`${cmd} ${arg1} ${arg2} ${arg3}` // test: CommandInjection
await $({ shell: false })`${cmd} ${arg1} ${arg2} ${arg3}` // test: CommandInjection
await $({ shell: false })`ssh ${arg1} ${arg2} ${arg3}` // test: CommandInjection
await execa(cmd, [arg1, arg2, arg3]); // test: CommandInjection
await execa(cmd, { shell: true }); // test: CommandInjection
await execa(cmd, { shell: true }); // test: CommandInjection
await execa(cmd, [arg1, arg2, arg3], { shell: true }); // test: CommandInjection
execaSync(cmd, [arg1, arg2, arg3]); // test: CommandInjection
execaSync(cmd, [arg1, arg2, arg3], { shell: true }); // test: CommandInjection
await execaCommand(cmd + arg1 + arg2 + arg3); // test: CommandInjection
await execaCommand(cmd + arg1 + arg2 + arg3, { shell: true }); // test: CommandInjection
execaCommandSync(cmd + arg1 + arg2 + arg3); // test: CommandInjection
execaCommandSync(cmd + arg1 + arg2 + arg3, { shell: true }); // test: CommandInjection
});

View File

@@ -0,0 +1,38 @@
import javascript
class InlineTest extends LineComment {
string tests;
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
string getPositiveTest() {
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
}
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
predicate inNode(DataFlow::Node n) {
this.getLocation().getFile() = n.getFile() and
this.getLocation().getStartLine() = n.getStartLine()
}
}
import experimental.semmle.javascript.Execa
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasPositiveTest(expectation) and
expectation = "CommandInjection" and
exists(SystemCommandExecution n |
t.inNode(n.getArgumentList()) or t.inNode(n.getACommandArgument())
)
}
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasPositiveTest(expectation) and
expectation = "CommandInjection" and
not exists(SystemCommandExecution n |
t.inNode(n.getArgumentList()) or t.inNode(n.getACommandArgument())
)
}

View File

@@ -0,0 +1,6 @@
passingPositiveTests
| PASSED | PathInjection | tests.js:9:43:9:64 | // test ... jection |
| PASSED | PathInjection | tests.js:12:50:12:71 | // test ... jection |
| PASSED | PathInjection | tests.js:15:61:15:82 | // test ... jection |
| PASSED | PathInjection | tests.js:18:73:18:94 | // test ... jection |
failingPositiveTests

View File

@@ -0,0 +1,19 @@
import { execa, $ } from 'execa';
import http from 'node:http'
import url from 'url'
http.createServer(async function (req, res) {
let filePath = url.parse(req.url, true).query["filePath"][0];
// Piping to stdin from a file
await $({ inputFile: filePath })`cat` // test: PathInjection
// Piping to stdin from a file
await execa('cat', { inputFile: filePath }); // test: PathInjection
// Piping Stdout to file
await execa('echo', ['example3']).pipeStdout(filePath); // test: PathInjection
// Piping all of command output to file
await execa('echo', ['example4'], { all: true }).pipeAll(filePath); // test: PathInjection
});

View File

@@ -0,0 +1,34 @@
import javascript
class InlineTest extends LineComment {
string tests;
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
string getPositiveTest() {
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
}
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
predicate inNode(DataFlow::Node n) {
this.getLocation().getFile() = n.getFile() and
this.getLocation().getStartLine() = n.getStartLine()
}
}
import experimental.semmle.javascript.Execa
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasPositiveTest(expectation) and
expectation = "PathInjection" and
exists(FileSystemReadAccess n | t.inNode(n.getAPathArgument()))
}
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasPositiveTest(expectation) and
expectation = "PathInjection" and
not exists(FileSystemReadAccess n | t.inNode(n.getAPathArgument()))
}

View File

@@ -0,0 +1,234 @@
nodes
| busybus.js:9:30:9:33 | file |
| busybus.js:9:30:9:33 | file |
| busybus.js:9:36:9:39 | info |
| busybus.js:9:36:9:39 | info |
| busybus.js:10:19:10:50 | { filen ... eType } |
| busybus.js:10:19:10:57 | encoding |
| busybus.js:10:19:10:57 | filename |
| busybus.js:10:19:10:57 | mimeType |
| busybus.js:10:21:10:28 | filename |
| busybus.js:10:31:10:38 | encoding |
| busybus.js:10:41:10:48 | mimeType |
| busybus.js:10:54:10:57 | info |
| busybus.js:12:18:12:25 | filename |
| busybus.js:12:18:12:25 | filename |
| busybus.js:12:28:12:35 | encoding |
| busybus.js:12:28:12:35 | encoding |
| busybus.js:12:38:12:45 | mimeType |
| busybus.js:12:38:12:45 | mimeType |
| busybus.js:13:23:13:23 | z |
| busybus.js:13:31:13:36 | sink() |
| busybus.js:13:31:13:36 | sink() |
| busybus.js:15:30:15:33 | data |
| busybus.js:15:30:15:33 | data |
| busybus.js:16:22:16:25 | data |
| busybus.js:16:22:16:25 | data |
| busybus.js:22:25:22:42 | data |
| busybus.js:22:32:22:42 | this.read() |
| busybus.js:22:32:22:42 | this.read() |
| busybus.js:23:26:23:29 | data |
| busybus.js:23:26:23:29 | data |
| busybus.js:27:25:27:28 | name |
| busybus.js:27:25:27:28 | name |
| busybus.js:27:31:27:33 | val |
| busybus.js:27:31:27:33 | val |
| busybus.js:27:36:27:39 | info |
| busybus.js:27:36:27:39 | info |
| busybus.js:28:18:28:21 | name |
| busybus.js:28:18:28:21 | name |
| busybus.js:28:24:28:26 | val |
| busybus.js:28:24:28:26 | val |
| busybus.js:28:29:28:32 | info |
| busybus.js:28:29:28:32 | info |
| dicer.js:12:23:12:26 | part |
| dicer.js:12:23:12:26 | part |
| dicer.js:13:19:13:24 | sink() |
| dicer.js:13:19:13:24 | sink() |
| dicer.js:14:28:14:33 | header |
| dicer.js:14:28:14:33 | header |
| dicer.js:16:22:16:27 | header |
| dicer.js:16:22:16:30 | header[h] |
| dicer.js:16:22:16:30 | header[h] |
| dicer.js:19:26:19:29 | data |
| dicer.js:19:26:19:29 | data |
| dicer.js:20:18:20:21 | data |
| dicer.js:20:18:20:21 | data |
| formidable.js:7:11:7:25 | [fields, files] |
| formidable.js:7:11:7:49 | fields |
| formidable.js:7:11:7:49 | files |
| formidable.js:7:12:7:17 | fields |
| formidable.js:7:20:7:24 | files |
| formidable.js:7:29:7:49 | await f ... se(req) |
| formidable.js:7:35:7:49 | form.parse(req) |
| formidable.js:7:35:7:49 | form.parse(req) |
| formidable.js:8:10:8:15 | fields |
| formidable.js:8:10:8:15 | fields |
| formidable.js:8:18:8:22 | files |
| formidable.js:8:18:8:22 | files |
| formidable.js:9:27:9:34 | formname |
| formidable.js:9:27:9:34 | formname |
| formidable.js:9:37:9:40 | file |
| formidable.js:9:37:9:40 | file |
| formidable.js:10:14:10:21 | formname |
| formidable.js:10:14:10:21 | formname |
| formidable.js:10:24:10:27 | file |
| formidable.js:10:24:10:27 | file |
| formidable.js:12:22:12:29 | formname |
| formidable.js:12:22:12:29 | formname |
| formidable.js:12:32:12:35 | file |
| formidable.js:12:32:12:35 | file |
| formidable.js:13:14:13:21 | formname |
| formidable.js:13:14:13:21 | formname |
| formidable.js:13:24:13:27 | file |
| formidable.js:13:24:13:27 | file |
| formidable.js:15:23:15:31 | fieldName |
| formidable.js:15:23:15:31 | fieldName |
| formidable.js:15:34:15:43 | fieldValue |
| formidable.js:15:34:15:43 | fieldValue |
| formidable.js:16:14:16:22 | fieldName |
| formidable.js:16:14:16:22 | fieldName |
| formidable.js:16:25:16:34 | fieldValue |
| formidable.js:16:25:16:34 | fieldValue |
| multiparty.js:8:22:8:25 | part |
| multiparty.js:8:22:8:25 | part |
| multiparty.js:9:14:9:17 | part |
| multiparty.js:9:14:9:17 | part |
| multiparty.js:10:19:10:24 | sink() |
| multiparty.js:10:19:10:24 | sink() |
| multiparty.js:14:37:14:42 | fields |
| multiparty.js:14:37:14:42 | fields |
| multiparty.js:14:45:14:49 | files |
| multiparty.js:14:45:14:49 | files |
| multiparty.js:15:14:15:19 | fields |
| multiparty.js:15:14:15:19 | fields |
| multiparty.js:15:22:15:26 | files |
| multiparty.js:15:22:15:26 | files |
edges
| busybus.js:9:30:9:33 | file | busybus.js:13:23:13:23 | z |
| busybus.js:9:30:9:33 | file | busybus.js:13:23:13:23 | z |
| busybus.js:9:36:9:39 | info | busybus.js:10:54:10:57 | info |
| busybus.js:9:36:9:39 | info | busybus.js:10:54:10:57 | info |
| busybus.js:10:19:10:50 | { filen ... eType } | busybus.js:10:21:10:28 | filename |
| busybus.js:10:19:10:50 | { filen ... eType } | busybus.js:10:31:10:38 | encoding |
| busybus.js:10:19:10:50 | { filen ... eType } | busybus.js:10:41:10:48 | mimeType |
| busybus.js:10:19:10:57 | encoding | busybus.js:12:28:12:35 | encoding |
| busybus.js:10:19:10:57 | encoding | busybus.js:12:28:12:35 | encoding |
| busybus.js:10:19:10:57 | filename | busybus.js:12:18:12:25 | filename |
| busybus.js:10:19:10:57 | filename | busybus.js:12:18:12:25 | filename |
| busybus.js:10:19:10:57 | mimeType | busybus.js:12:38:12:45 | mimeType |
| busybus.js:10:19:10:57 | mimeType | busybus.js:12:38:12:45 | mimeType |
| busybus.js:10:21:10:28 | filename | busybus.js:10:19:10:57 | filename |
| busybus.js:10:31:10:38 | encoding | busybus.js:10:19:10:57 | encoding |
| busybus.js:10:41:10:48 | mimeType | busybus.js:10:19:10:57 | mimeType |
| busybus.js:10:54:10:57 | info | busybus.js:10:19:10:50 | { filen ... eType } |
| busybus.js:13:23:13:23 | z | busybus.js:13:31:13:36 | sink() |
| busybus.js:13:23:13:23 | z | busybus.js:13:31:13:36 | sink() |
| busybus.js:15:30:15:33 | data | busybus.js:16:22:16:25 | data |
| busybus.js:15:30:15:33 | data | busybus.js:16:22:16:25 | data |
| busybus.js:15:30:15:33 | data | busybus.js:16:22:16:25 | data |
| busybus.js:15:30:15:33 | data | busybus.js:16:22:16:25 | data |
| busybus.js:22:25:22:42 | data | busybus.js:23:26:23:29 | data |
| busybus.js:22:25:22:42 | data | busybus.js:23:26:23:29 | data |
| busybus.js:22:32:22:42 | this.read() | busybus.js:22:25:22:42 | data |
| busybus.js:22:32:22:42 | this.read() | busybus.js:22:25:22:42 | data |
| busybus.js:27:25:27:28 | name | busybus.js:28:18:28:21 | name |
| busybus.js:27:25:27:28 | name | busybus.js:28:18:28:21 | name |
| busybus.js:27:25:27:28 | name | busybus.js:28:18:28:21 | name |
| busybus.js:27:25:27:28 | name | busybus.js:28:18:28:21 | name |
| busybus.js:27:31:27:33 | val | busybus.js:28:24:28:26 | val |
| busybus.js:27:31:27:33 | val | busybus.js:28:24:28:26 | val |
| busybus.js:27:31:27:33 | val | busybus.js:28:24:28:26 | val |
| busybus.js:27:31:27:33 | val | busybus.js:28:24:28:26 | val |
| busybus.js:27:36:27:39 | info | busybus.js:28:29:28:32 | info |
| busybus.js:27:36:27:39 | info | busybus.js:28:29:28:32 | info |
| busybus.js:27:36:27:39 | info | busybus.js:28:29:28:32 | info |
| busybus.js:27:36:27:39 | info | busybus.js:28:29:28:32 | info |
| dicer.js:12:23:12:26 | part | dicer.js:13:19:13:24 | sink() |
| dicer.js:12:23:12:26 | part | dicer.js:13:19:13:24 | sink() |
| dicer.js:12:23:12:26 | part | dicer.js:13:19:13:24 | sink() |
| dicer.js:12:23:12:26 | part | dicer.js:13:19:13:24 | sink() |
| dicer.js:14:28:14:33 | header | dicer.js:16:22:16:27 | header |
| dicer.js:14:28:14:33 | header | dicer.js:16:22:16:27 | header |
| dicer.js:16:22:16:27 | header | dicer.js:16:22:16:30 | header[h] |
| dicer.js:16:22:16:27 | header | dicer.js:16:22:16:30 | header[h] |
| dicer.js:19:26:19:29 | data | dicer.js:20:18:20:21 | data |
| dicer.js:19:26:19:29 | data | dicer.js:20:18:20:21 | data |
| dicer.js:19:26:19:29 | data | dicer.js:20:18:20:21 | data |
| dicer.js:19:26:19:29 | data | dicer.js:20:18:20:21 | data |
| formidable.js:7:11:7:25 | [fields, files] | formidable.js:7:12:7:17 | fields |
| formidable.js:7:11:7:25 | [fields, files] | formidable.js:7:20:7:24 | files |
| formidable.js:7:11:7:49 | fields | formidable.js:8:10:8:15 | fields |
| formidable.js:7:11:7:49 | fields | formidable.js:8:10:8:15 | fields |
| formidable.js:7:11:7:49 | files | formidable.js:8:18:8:22 | files |
| formidable.js:7:11:7:49 | files | formidable.js:8:18:8:22 | files |
| formidable.js:7:12:7:17 | fields | formidable.js:7:11:7:49 | fields |
| formidable.js:7:20:7:24 | files | formidable.js:7:11:7:49 | files |
| formidable.js:7:29:7:49 | await f ... se(req) | formidable.js:7:11:7:25 | [fields, files] |
| formidable.js:7:35:7:49 | form.parse(req) | formidable.js:7:29:7:49 | await f ... se(req) |
| formidable.js:7:35:7:49 | form.parse(req) | formidable.js:7:29:7:49 | await f ... se(req) |
| formidable.js:9:27:9:34 | formname | formidable.js:10:14:10:21 | formname |
| formidable.js:9:27:9:34 | formname | formidable.js:10:14:10:21 | formname |
| formidable.js:9:27:9:34 | formname | formidable.js:10:14:10:21 | formname |
| formidable.js:9:27:9:34 | formname | formidable.js:10:14:10:21 | formname |
| formidable.js:9:37:9:40 | file | formidable.js:10:24:10:27 | file |
| formidable.js:9:37:9:40 | file | formidable.js:10:24:10:27 | file |
| formidable.js:9:37:9:40 | file | formidable.js:10:24:10:27 | file |
| formidable.js:9:37:9:40 | file | formidable.js:10:24:10:27 | file |
| formidable.js:12:22:12:29 | formname | formidable.js:13:14:13:21 | formname |
| formidable.js:12:22:12:29 | formname | formidable.js:13:14:13:21 | formname |
| formidable.js:12:22:12:29 | formname | formidable.js:13:14:13:21 | formname |
| formidable.js:12:22:12:29 | formname | formidable.js:13:14:13:21 | formname |
| formidable.js:12:32:12:35 | file | formidable.js:13:24:13:27 | file |
| formidable.js:12:32:12:35 | file | formidable.js:13:24:13:27 | file |
| formidable.js:12:32:12:35 | file | formidable.js:13:24:13:27 | file |
| formidable.js:12:32:12:35 | file | formidable.js:13:24:13:27 | file |
| formidable.js:15:23:15:31 | fieldName | formidable.js:16:14:16:22 | fieldName |
| formidable.js:15:23:15:31 | fieldName | formidable.js:16:14:16:22 | fieldName |
| formidable.js:15:23:15:31 | fieldName | formidable.js:16:14:16:22 | fieldName |
| formidable.js:15:23:15:31 | fieldName | formidable.js:16:14:16:22 | fieldName |
| formidable.js:15:34:15:43 | fieldValue | formidable.js:16:25:16:34 | fieldValue |
| formidable.js:15:34:15:43 | fieldValue | formidable.js:16:25:16:34 | fieldValue |
| formidable.js:15:34:15:43 | fieldValue | formidable.js:16:25:16:34 | fieldValue |
| formidable.js:15:34:15:43 | fieldValue | formidable.js:16:25:16:34 | fieldValue |
| multiparty.js:8:22:8:25 | part | multiparty.js:9:14:9:17 | part |
| multiparty.js:8:22:8:25 | part | multiparty.js:9:14:9:17 | part |
| multiparty.js:8:22:8:25 | part | multiparty.js:9:14:9:17 | part |
| multiparty.js:8:22:8:25 | part | multiparty.js:9:14:9:17 | part |
| multiparty.js:8:22:8:25 | part | multiparty.js:10:19:10:24 | sink() |
| multiparty.js:8:22:8:25 | part | multiparty.js:10:19:10:24 | sink() |
| multiparty.js:8:22:8:25 | part | multiparty.js:10:19:10:24 | sink() |
| multiparty.js:8:22:8:25 | part | multiparty.js:10:19:10:24 | sink() |
| multiparty.js:14:37:14:42 | fields | multiparty.js:15:14:15:19 | fields |
| multiparty.js:14:37:14:42 | fields | multiparty.js:15:14:15:19 | fields |
| multiparty.js:14:37:14:42 | fields | multiparty.js:15:14:15:19 | fields |
| multiparty.js:14:37:14:42 | fields | multiparty.js:15:14:15:19 | fields |
| multiparty.js:14:45:14:49 | files | multiparty.js:15:22:15:26 | files |
| multiparty.js:14:45:14:49 | files | multiparty.js:15:22:15:26 | files |
| multiparty.js:14:45:14:49 | files | multiparty.js:15:22:15:26 | files |
| multiparty.js:14:45:14:49 | files | multiparty.js:15:22:15:26 | files |
#select
| busybus.js:12:18:12:25 | filename | busybus.js:9:36:9:39 | info | busybus.js:12:18:12:25 | filename | This entity depends on a $@. | busybus.js:9:36:9:39 | info | user-provided value |
| busybus.js:12:28:12:35 | encoding | busybus.js:9:36:9:39 | info | busybus.js:12:28:12:35 | encoding | This entity depends on a $@. | busybus.js:9:36:9:39 | info | user-provided value |
| busybus.js:12:38:12:45 | mimeType | busybus.js:9:36:9:39 | info | busybus.js:12:38:12:45 | mimeType | This entity depends on a $@. | busybus.js:9:36:9:39 | info | user-provided value |
| busybus.js:13:31:13:36 | sink() | busybus.js:9:30:9:33 | file | busybus.js:13:31:13:36 | sink() | This entity depends on a $@. | busybus.js:9:30:9:33 | file | user-provided value |
| busybus.js:16:22:16:25 | data | busybus.js:15:30:15:33 | data | busybus.js:16:22:16:25 | data | This entity depends on a $@. | busybus.js:15:30:15:33 | data | user-provided value |
| busybus.js:23:26:23:29 | data | busybus.js:22:32:22:42 | this.read() | busybus.js:23:26:23:29 | data | This entity depends on a $@. | busybus.js:22:32:22:42 | this.read() | user-provided value |
| busybus.js:28:18:28:21 | name | busybus.js:27:25:27:28 | name | busybus.js:28:18:28:21 | name | This entity depends on a $@. | busybus.js:27:25:27:28 | name | user-provided value |
| busybus.js:28:24:28:26 | val | busybus.js:27:31:27:33 | val | busybus.js:28:24:28:26 | val | This entity depends on a $@. | busybus.js:27:31:27:33 | val | user-provided value |
| busybus.js:28:29:28:32 | info | busybus.js:27:36:27:39 | info | busybus.js:28:29:28:32 | info | This entity depends on a $@. | busybus.js:27:36:27:39 | info | user-provided value |
| dicer.js:13:19:13:24 | sink() | dicer.js:12:23:12:26 | part | dicer.js:13:19:13:24 | sink() | This entity depends on a $@. | dicer.js:12:23:12:26 | part | user-provided value |
| dicer.js:16:22:16:30 | header[h] | dicer.js:14:28:14:33 | header | dicer.js:16:22:16:30 | header[h] | This entity depends on a $@. | dicer.js:14:28:14:33 | header | user-provided value |
| dicer.js:20:18:20:21 | data | dicer.js:19:26:19:29 | data | dicer.js:20:18:20:21 | data | This entity depends on a $@. | dicer.js:19:26:19:29 | data | user-provided value |
| formidable.js:8:10:8:15 | fields | formidable.js:7:35:7:49 | form.parse(req) | formidable.js:8:10:8:15 | fields | This entity depends on a $@. | formidable.js:7:35:7:49 | form.parse(req) | user-provided value |
| formidable.js:8:18:8:22 | files | formidable.js:7:35:7:49 | form.parse(req) | formidable.js:8:18:8:22 | files | This entity depends on a $@. | formidable.js:7:35:7:49 | form.parse(req) | user-provided value |
| formidable.js:10:14:10:21 | formname | formidable.js:9:27:9:34 | formname | formidable.js:10:14:10:21 | formname | This entity depends on a $@. | formidable.js:9:27:9:34 | formname | user-provided value |
| formidable.js:10:24:10:27 | file | formidable.js:9:37:9:40 | file | formidable.js:10:24:10:27 | file | This entity depends on a $@. | formidable.js:9:37:9:40 | file | user-provided value |
| formidable.js:13:14:13:21 | formname | formidable.js:12:22:12:29 | formname | formidable.js:13:14:13:21 | formname | This entity depends on a $@. | formidable.js:12:22:12:29 | formname | user-provided value |
| formidable.js:13:24:13:27 | file | formidable.js:12:32:12:35 | file | formidable.js:13:24:13:27 | file | This entity depends on a $@. | formidable.js:12:32:12:35 | file | user-provided value |
| formidable.js:16:14:16:22 | fieldName | formidable.js:15:23:15:31 | fieldName | formidable.js:16:14:16:22 | fieldName | This entity depends on a $@. | formidable.js:15:23:15:31 | fieldName | user-provided value |
| formidable.js:16:25:16:34 | fieldValue | formidable.js:15:34:15:43 | fieldValue | formidable.js:16:25:16:34 | fieldValue | This entity depends on a $@. | formidable.js:15:34:15:43 | fieldValue | user-provided value |
| multiparty.js:9:14:9:17 | part | multiparty.js:8:22:8:25 | part | multiparty.js:9:14:9:17 | part | This entity depends on a $@. | multiparty.js:8:22:8:25 | part | user-provided value |
| multiparty.js:10:19:10:24 | sink() | multiparty.js:8:22:8:25 | part | multiparty.js:10:19:10:24 | sink() | This entity depends on a $@. | multiparty.js:8:22:8:25 | part | user-provided value |
| multiparty.js:15:14:15:19 | fields | multiparty.js:14:37:14:42 | fields | multiparty.js:15:14:15:19 | fields | This entity depends on a $@. | multiparty.js:14:37:14:42 | fields | user-provided value |
| multiparty.js:15:22:15:26 | files | multiparty.js:14:45:14:49 | files | multiparty.js:15:22:15:26 | files | This entity depends on a $@. | multiparty.js:14:45:14:49 | files | user-provided value |

View File

@@ -0,0 +1,34 @@
/**
* @name Remote Form Flow Sources
* @description Using remote user controlled sources from Forms
* @kind path-problem
* @problem.severity error
* @security-severity 5
* @precision high
* @id js/remote-flow-source
* @tags correctness
* security
*/
import javascript
import DataFlow::PathGraph
import experimental.semmle.javascript.FormParsers
/**
* A taint-tracking configuration for test
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "RemoteFlowSourcesOUserForm" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink = API::moduleImport("sink").getAParameter().asSink() or
sink = API::moduleImport("sink").getReturn().asSource()
}
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This entity depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,33 @@
const http = require('http');
const zlib = require('node:zlib');
const busboy = require('busboy');
const sink = require('sink');
http.createServer((req, res) => {
if (req.method === 'POST') {
const bb = busboy({ headers: req.headers });
bb.on('file', (name, file, info) => {
const { filename, encoding, mimeType } = info;
const z = zlib.createGzip();
sink(filename, encoding, mimeType) // sink
file.pipe(z).pipe(sink())
file.on('data', (data) => {
sink(data)
})
file.on('readable', function () {
// There is some data to read now.
let data;
while ((data = this.read()) !== null) {
sink(data)
}
});
});
bb.on('field', (name, val, info) => {
sink(name, val, info)
});
}
}).listen(8000, () => {
console.log('Listening for requests');
});

View File

@@ -0,0 +1,25 @@
const { inspect } = require('util');
const http = require('http');
const Dicer = require('dicer');
const sink = require('sink');
const PORT = 8080;
http.createServer((req, res) => {
let m;
const dicer = new Dicer({ boundary: m[1] || m[2] });
dicer.on('part', (part) => {
part.pipe(sink())
part.on('header', (header) => {
for (h in header) {
sink(header[h])
}
});
part.on('data', (data) => {
sink(data)
});
});
}).listen(PORT, () => {
console.log(`Listening for requests on port ${PORT}`);
});

View File

@@ -0,0 +1,22 @@
import http from 'node:http';
import formidable from 'formidable';
const sink = require('sink');
const server = http.createServer(async (req, res) => {
const form = formidable({});
const [fields, files] = await form.parse(req);
sink(fields, files)
form.on('fileBegin', (formname, file) => {
sink(formname, file)
});
form.on('file', (formname, file) => {
sink(formname, file)
});
form.on('field', (fieldName, fieldValue) => {
sink(fieldName, fieldValue)
});
});
server.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...');
});

View File

@@ -0,0 +1,19 @@
var multiparty = require('multiparty');
var http = require('http');
var util = require('util');
const sink = require('sink');
http.createServer(function (req, res) {
var form = new multiparty.Form();
form.on('part', (part) => {
sink(part)
part.pipe(sink())
});
var form2 = new multiparty.Form();
form2.parse(req, function (err, fields, files) {
sink(fields, files)
});
form2.parse(req);
}).listen(8080);

View File

@@ -0,0 +1,51 @@
nodes
| test.js:5:11:5:44 | payload |
| test.js:5:21:5:44 | req.que ... rameter |
| test.js:5:21:5:44 | req.que ... rameter |
| test.js:6:9:6:43 | payloadURL |
| test.js:6:22:6:43 | new URL ... + sth) |
| test.js:6:30:6:36 | payload |
| test.js:6:30:6:42 | payload + sth |
| test.js:7:16:7:25 | payloadURL |
| test.js:7:16:7:25 | payloadURL |
| test.js:9:5:9:39 | payloadURL |
| test.js:9:18:9:39 | new URL ... + sth) |
| test.js:9:26:9:32 | payload |
| test.js:9:26:9:38 | payload + sth |
| test.js:10:16:10:25 | payloadURL |
| test.js:10:16:10:25 | payloadURL |
| test.js:17:11:17:44 | payload |
| test.js:17:21:17:44 | req.que ... rameter |
| test.js:17:21:17:44 | req.que ... rameter |
| test.js:18:18:18:24 | payload |
| test.js:18:18:18:24 | payload |
| test.js:19:18:19:24 | payload |
| test.js:19:18:19:30 | payload + sth |
| test.js:19:18:19:30 | payload + sth |
edges
| test.js:5:11:5:44 | payload | test.js:6:30:6:36 | payload |
| test.js:5:11:5:44 | payload | test.js:9:26:9:32 | payload |
| test.js:5:21:5:44 | req.que ... rameter | test.js:5:11:5:44 | payload |
| test.js:5:21:5:44 | req.que ... rameter | test.js:5:11:5:44 | payload |
| test.js:6:9:6:43 | payloadURL | test.js:7:16:7:25 | payloadURL |
| test.js:6:9:6:43 | payloadURL | test.js:7:16:7:25 | payloadURL |
| test.js:6:22:6:43 | new URL ... + sth) | test.js:6:9:6:43 | payloadURL |
| test.js:6:30:6:36 | payload | test.js:6:30:6:42 | payload + sth |
| test.js:6:30:6:42 | payload + sth | test.js:6:22:6:43 | new URL ... + sth) |
| test.js:9:5:9:39 | payloadURL | test.js:10:16:10:25 | payloadURL |
| test.js:9:5:9:39 | payloadURL | test.js:10:16:10:25 | payloadURL |
| test.js:9:18:9:39 | new URL ... + sth) | test.js:9:5:9:39 | payloadURL |
| test.js:9:26:9:32 | payload | test.js:9:26:9:38 | payload + sth |
| test.js:9:26:9:38 | payload + sth | test.js:9:18:9:39 | new URL ... + sth) |
| test.js:17:11:17:44 | payload | test.js:18:18:18:24 | payload |
| test.js:17:11:17:44 | payload | test.js:18:18:18:24 | payload |
| test.js:17:11:17:44 | payload | test.js:19:18:19:24 | payload |
| test.js:17:21:17:44 | req.que ... rameter | test.js:17:11:17:44 | payload |
| test.js:17:21:17:44 | req.que ... rameter | test.js:17:11:17:44 | payload |
| test.js:19:18:19:24 | payload | test.js:19:18:19:30 | payload + sth |
| test.js:19:18:19:24 | payload | test.js:19:18:19:30 | payload + sth |
#select
| test.js:7:16:7:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:7:16:7:25 | payloadURL | This command line depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
| test.js:10:16:10:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:10:16:10:25 | payloadURL | This command line depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
| test.js:18:18:18:24 | payload | test.js:17:21:17:44 | req.que ... rameter | test.js:18:18:18:24 | payload | This command line depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value |
| test.js:19:18:19:30 | payload + sth | test.js:17:21:17:44 | req.que ... rameter | test.js:19:18:19:30 | payload + sth | This command line depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-094-dataURL/CodeInjection.ql

View File

@@ -0,0 +1,22 @@
const { Worker } = require('node:worker_threads');
var app = require('express')();
app.post('/path', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
let payloadURL = new URL(payload + sth) // NOT OK
new Worker(payloadURL);
payloadURL = new URL(payload + sth) // NOT OK
new Worker(payloadURL);
payloadURL = new URL(sth + payload) // OK
new Worker(payloadURL);
});
app.post('/path2', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
await import(payload) // NOT OK
await import(payload + sth) // NOT OK
await import(sth + payload) // OK
});

View File

@@ -0,0 +1,55 @@
nodes
| test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:5:9:5:39 | EnvKey |
| test.js:5:9:5:39 | EnvValue |
| test.js:5:11:5:18 | EnvValue |
| test.js:5:21:5:26 | EnvKey |
| test.js:5:32:5:39 | req.body |
| test.js:5:32:5:39 | req.body |
| test.js:6:15:6:20 | EnvKey |
| test.js:6:15:6:20 | EnvKey |
| test.js:6:25:6:32 | EnvValue |
| test.js:6:25:6:32 | EnvValue |
| test.js:7:15:7:20 | EnvKey |
| test.js:7:15:7:20 | EnvKey |
| test.js:7:25:7:32 | EnvValue |
| test.js:7:25:7:32 | EnvValue |
| test.js:13:9:13:28 | { EnvValue, EnvKey } |
| test.js:13:9:13:39 | EnvKey |
| test.js:13:9:13:39 | EnvValue |
| test.js:13:11:13:18 | EnvValue |
| test.js:13:21:13:26 | EnvKey |
| test.js:13:32:13:39 | req.body |
| test.js:13:32:13:39 | req.body |
| test.js:15:15:15:20 | EnvKey |
| test.js:15:15:15:20 | EnvKey |
| test.js:16:26:16:33 | EnvValue |
| test.js:16:26:16:33 | EnvValue |
edges
| test.js:5:9:5:28 | { EnvValue, EnvKey } | test.js:5:11:5:18 | EnvValue |
| test.js:5:9:5:28 | { EnvValue, EnvKey } | test.js:5:21:5:26 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:6:15:6:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:6:15:6:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:7:15:7:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:7:15:7:20 | EnvKey |
| test.js:5:9:5:39 | EnvValue | test.js:6:25:6:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:6:25:6:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:7:25:7:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:7:25:7:32 | EnvValue |
| test.js:5:11:5:18 | EnvValue | test.js:5:9:5:39 | EnvValue |
| test.js:5:21:5:26 | EnvKey | test.js:5:9:5:39 | EnvKey |
| test.js:5:32:5:39 | req.body | test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:5:32:5:39 | req.body | test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:13:9:13:28 | { EnvValue, EnvKey } | test.js:13:11:13:18 | EnvValue |
| test.js:13:9:13:28 | { EnvValue, EnvKey } | test.js:13:21:13:26 | EnvKey |
| test.js:13:9:13:39 | EnvKey | test.js:15:15:15:20 | EnvKey |
| test.js:13:9:13:39 | EnvKey | test.js:15:15:15:20 | EnvKey |
| test.js:13:9:13:39 | EnvValue | test.js:16:26:16:33 | EnvValue |
| test.js:13:9:13:39 | EnvValue | test.js:16:26:16:33 | EnvValue |
| test.js:13:11:13:18 | EnvValue | test.js:13:9:13:39 | EnvValue |
| test.js:13:21:13:26 | EnvKey | test.js:13:9:13:39 | EnvKey |
| test.js:13:32:13:39 | req.body | test.js:13:9:13:28 | { EnvValue, EnvKey } |
| test.js:13:32:13:39 | req.body | test.js:13:9:13:28 | { EnvValue, EnvKey } |
#select
| test.js:6:15:6:20 | EnvKey | test.js:5:32:5:39 | req.body | test.js:6:15:6:20 | EnvKey | arbitrary environment variable assignment from this $@. | test.js:5:32:5:39 | req.body | user controllable source |
| test.js:7:15:7:20 | EnvKey | test.js:5:32:5:39 | req.body | test.js:7:15:7:20 | EnvKey | arbitrary environment variable assignment from this $@. | test.js:5:32:5:39 | req.body | user controllable source |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-099/EnvValueAndKeyInjection.ql

View File

@@ -0,0 +1,19 @@
const http = require('node:http');
http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;
process.env[EnvKey] = EnvValue; // NOT OK
process.env[EnvKey] = EnvValue; // NOT OK
res.end('env has been injected!');
});
http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;
process.env[EnvKey] = "constant" // OK
process.env.constant = EnvValue // OK
res.end('env has been injected!');
});

View File

@@ -0,0 +1,27 @@
nodes
| test.js:4:9:4:20 | { EnvValue } |
| test.js:4:9:4:31 | EnvValue |
| test.js:4:11:4:18 | EnvValue |
| test.js:4:24:4:31 | req.body |
| test.js:4:24:4:31 | req.body |
| test.js:5:35:5:42 | EnvValue |
| test.js:5:35:5:42 | EnvValue |
| test.js:6:23:6:30 | EnvValue |
| test.js:6:23:6:30 | EnvValue |
| test.js:7:22:7:29 | EnvValue |
| test.js:7:22:7:29 | EnvValue |
edges
| test.js:4:9:4:20 | { EnvValue } | test.js:4:11:4:18 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:5:35:5:42 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:5:35:5:42 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:6:23:6:30 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:6:23:6:30 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:7:22:7:29 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:7:22:7:29 | EnvValue |
| test.js:4:11:4:18 | EnvValue | test.js:4:9:4:31 | EnvValue |
| test.js:4:24:4:31 | req.body | test.js:4:9:4:20 | { EnvValue } |
| test.js:4:24:4:31 | req.body | test.js:4:9:4:20 | { EnvValue } |
#select
| test.js:5:35:5:42 | EnvValue | test.js:4:24:4:31 | req.body | test.js:5:35:5:42 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |
| test.js:6:23:6:30 | EnvValue | test.js:4:24:4:31 | req.body | test.js:6:23:6:30 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |
| test.js:7:22:7:29 | EnvValue | test.js:4:24:4:31 | req.body | test.js:7:22:7:29 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-099/EnvValueInjection.ql

View File

@@ -0,0 +1,10 @@
const http = require('node:http');
http.createServer((req, res) => {
const { EnvValue } = req.body;
process.env["A_Critical_Env"] = EnvValue; // NOT OK
process.env[AKey] = EnvValue; // NOT OK
process.env.AKey = EnvValue; // NOT OK
res.end('env has been injected!');
});

View File

@@ -0,0 +1,48 @@
const express = require('express')
const jwtJsonwebtoken = require('jsonwebtoken');
function getSecret() {
return "A Safe generated random key"
}
function aJWT() {
return "A JWT provided by user"
}
(function () {
const UserToken = aJwt()
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken) // NOT OK
})();
(function () {
const UserToken = aJwt()
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken) // NOT OK
jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // NOT OK
})();
(function () {
const UserToken = aJwt()
// GOOD: with signature verification
jwtJsonwebtoken.verify(UserToken, getSecret()) // OK
})();
(function () {
const UserToken = aJwt()
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken) // OK
jwtJsonwebtoken.verify(UserToken, getSecret()) // OK
})();
(function () {
const UserToken = aJwt()
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken) // OK
jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256"] }) // OK
})();

View File

@@ -0,0 +1,141 @@
nodes
| JsonWebToken.js:13:11:13:28 | UserToken |
| JsonWebToken.js:13:23:13:28 | aJwt() |
| JsonWebToken.js:13:23:13:28 | aJwt() |
| JsonWebToken.js:16:28:16:36 | UserToken |
| JsonWebToken.js:16:28:16:36 | UserToken |
| JsonWebToken.js:20:11:20:28 | UserToken |
| JsonWebToken.js:20:23:20:28 | aJwt() |
| JsonWebToken.js:20:23:20:28 | aJwt() |
| JsonWebToken.js:23:28:23:36 | UserToken |
| JsonWebToken.js:23:28:23:36 | UserToken |
| JsonWebToken.js:24:28:24:36 | UserToken |
| JsonWebToken.js:24:28:24:36 | UserToken |
| JsonWebToken.js:28:11:28:28 | UserToken |
| JsonWebToken.js:28:23:28:28 | aJwt() |
| JsonWebToken.js:28:23:28:28 | aJwt() |
| JsonWebToken.js:31:28:31:36 | UserToken |
| JsonWebToken.js:31:28:31:36 | UserToken |
| JsonWebToken.js:35:11:35:28 | UserToken |
| JsonWebToken.js:35:23:35:28 | aJwt() |
| JsonWebToken.js:35:23:35:28 | aJwt() |
| JsonWebToken.js:38:28:38:36 | UserToken |
| JsonWebToken.js:38:28:38:36 | UserToken |
| JsonWebToken.js:39:28:39:36 | UserToken |
| JsonWebToken.js:39:28:39:36 | UserToken |
| JsonWebToken.js:43:11:43:28 | UserToken |
| JsonWebToken.js:43:23:43:28 | aJwt() |
| JsonWebToken.js:43:23:43:28 | aJwt() |
| JsonWebToken.js:46:28:46:36 | UserToken |
| JsonWebToken.js:46:28:46:36 | UserToken |
| JsonWebToken.js:47:28:47:36 | UserToken |
| JsonWebToken.js:47:28:47:36 | UserToken |
| jose.js:12:11:12:28 | UserToken |
| jose.js:12:23:12:28 | aJwt() |
| jose.js:12:23:12:28 | aJwt() |
| jose.js:15:20:15:28 | UserToken |
| jose.js:15:20:15:28 | UserToken |
| jose.js:19:11:19:28 | UserToken |
| jose.js:19:23:19:28 | aJwt() |
| jose.js:19:23:19:28 | aJwt() |
| jose.js:22:20:22:28 | UserToken |
| jose.js:22:20:22:28 | UserToken |
| jose.js:23:26:23:34 | UserToken |
| jose.js:23:26:23:34 | UserToken |
| jose.js:27:11:27:28 | UserToken |
| jose.js:27:23:27:28 | aJwt() |
| jose.js:27:23:27:28 | aJwt() |
| jose.js:30:26:30:34 | UserToken |
| jose.js:30:26:30:34 | UserToken |
| jwtDecode.js:13:11:13:28 | UserToken |
| jwtDecode.js:13:23:13:28 | aJwt() |
| jwtDecode.js:13:23:13:28 | aJwt() |
| jwtDecode.js:17:16:17:24 | UserToken |
| jwtDecode.js:17:16:17:24 | UserToken |
| jwtSimple.js:13:11:13:28 | UserToken |
| jwtSimple.js:13:23:13:28 | aJwt() |
| jwtSimple.js:13:23:13:28 | aJwt() |
| jwtSimple.js:16:23:16:31 | UserToken |
| jwtSimple.js:16:23:16:31 | UserToken |
| jwtSimple.js:20:11:20:28 | UserToken |
| jwtSimple.js:20:23:20:28 | aJwt() |
| jwtSimple.js:20:23:20:28 | aJwt() |
| jwtSimple.js:23:23:23:31 | UserToken |
| jwtSimple.js:23:23:23:31 | UserToken |
| jwtSimple.js:24:23:24:31 | UserToken |
| jwtSimple.js:24:23:24:31 | UserToken |
| jwtSimple.js:28:11:28:28 | UserToken |
| jwtSimple.js:28:23:28:28 | aJwt() |
| jwtSimple.js:28:23:28:28 | aJwt() |
| jwtSimple.js:31:23:31:31 | UserToken |
| jwtSimple.js:31:23:31:31 | UserToken |
| jwtSimple.js:32:23:32:31 | UserToken |
| jwtSimple.js:32:23:32:31 | UserToken |
edges
| JsonWebToken.js:13:11:13:28 | UserToken | JsonWebToken.js:16:28:16:36 | UserToken |
| JsonWebToken.js:13:11:13:28 | UserToken | JsonWebToken.js:16:28:16:36 | UserToken |
| JsonWebToken.js:13:23:13:28 | aJwt() | JsonWebToken.js:13:11:13:28 | UserToken |
| JsonWebToken.js:13:23:13:28 | aJwt() | JsonWebToken.js:13:11:13:28 | UserToken |
| JsonWebToken.js:20:11:20:28 | UserToken | JsonWebToken.js:23:28:23:36 | UserToken |
| JsonWebToken.js:20:11:20:28 | UserToken | JsonWebToken.js:23:28:23:36 | UserToken |
| JsonWebToken.js:20:11:20:28 | UserToken | JsonWebToken.js:24:28:24:36 | UserToken |
| JsonWebToken.js:20:11:20:28 | UserToken | JsonWebToken.js:24:28:24:36 | UserToken |
| JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:20:11:20:28 | UserToken |
| JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:20:11:20:28 | UserToken |
| JsonWebToken.js:28:11:28:28 | UserToken | JsonWebToken.js:31:28:31:36 | UserToken |
| JsonWebToken.js:28:11:28:28 | UserToken | JsonWebToken.js:31:28:31:36 | UserToken |
| JsonWebToken.js:28:23:28:28 | aJwt() | JsonWebToken.js:28:11:28:28 | UserToken |
| JsonWebToken.js:28:23:28:28 | aJwt() | JsonWebToken.js:28:11:28:28 | UserToken |
| JsonWebToken.js:35:11:35:28 | UserToken | JsonWebToken.js:38:28:38:36 | UserToken |
| JsonWebToken.js:35:11:35:28 | UserToken | JsonWebToken.js:38:28:38:36 | UserToken |
| JsonWebToken.js:35:11:35:28 | UserToken | JsonWebToken.js:39:28:39:36 | UserToken |
| JsonWebToken.js:35:11:35:28 | UserToken | JsonWebToken.js:39:28:39:36 | UserToken |
| JsonWebToken.js:35:23:35:28 | aJwt() | JsonWebToken.js:35:11:35:28 | UserToken |
| JsonWebToken.js:35:23:35:28 | aJwt() | JsonWebToken.js:35:11:35:28 | UserToken |
| JsonWebToken.js:43:11:43:28 | UserToken | JsonWebToken.js:46:28:46:36 | UserToken |
| JsonWebToken.js:43:11:43:28 | UserToken | JsonWebToken.js:46:28:46:36 | UserToken |
| JsonWebToken.js:43:11:43:28 | UserToken | JsonWebToken.js:47:28:47:36 | UserToken |
| JsonWebToken.js:43:11:43:28 | UserToken | JsonWebToken.js:47:28:47:36 | UserToken |
| JsonWebToken.js:43:23:43:28 | aJwt() | JsonWebToken.js:43:11:43:28 | UserToken |
| JsonWebToken.js:43:23:43:28 | aJwt() | JsonWebToken.js:43:11:43:28 | UserToken |
| jose.js:12:11:12:28 | UserToken | jose.js:15:20:15:28 | UserToken |
| jose.js:12:11:12:28 | UserToken | jose.js:15:20:15:28 | UserToken |
| jose.js:12:23:12:28 | aJwt() | jose.js:12:11:12:28 | UserToken |
| jose.js:12:23:12:28 | aJwt() | jose.js:12:11:12:28 | UserToken |
| jose.js:19:11:19:28 | UserToken | jose.js:22:20:22:28 | UserToken |
| jose.js:19:11:19:28 | UserToken | jose.js:22:20:22:28 | UserToken |
| jose.js:19:11:19:28 | UserToken | jose.js:23:26:23:34 | UserToken |
| jose.js:19:11:19:28 | UserToken | jose.js:23:26:23:34 | UserToken |
| jose.js:19:23:19:28 | aJwt() | jose.js:19:11:19:28 | UserToken |
| jose.js:19:23:19:28 | aJwt() | jose.js:19:11:19:28 | UserToken |
| jose.js:27:11:27:28 | UserToken | jose.js:30:26:30:34 | UserToken |
| jose.js:27:11:27:28 | UserToken | jose.js:30:26:30:34 | UserToken |
| jose.js:27:23:27:28 | aJwt() | jose.js:27:11:27:28 | UserToken |
| jose.js:27:23:27:28 | aJwt() | jose.js:27:11:27:28 | UserToken |
| jwtDecode.js:13:11:13:28 | UserToken | jwtDecode.js:17:16:17:24 | UserToken |
| jwtDecode.js:13:11:13:28 | UserToken | jwtDecode.js:17:16:17:24 | UserToken |
| jwtDecode.js:13:23:13:28 | aJwt() | jwtDecode.js:13:11:13:28 | UserToken |
| jwtDecode.js:13:23:13:28 | aJwt() | jwtDecode.js:13:11:13:28 | UserToken |
| jwtSimple.js:13:11:13:28 | UserToken | jwtSimple.js:16:23:16:31 | UserToken |
| jwtSimple.js:13:11:13:28 | UserToken | jwtSimple.js:16:23:16:31 | UserToken |
| jwtSimple.js:13:23:13:28 | aJwt() | jwtSimple.js:13:11:13:28 | UserToken |
| jwtSimple.js:13:23:13:28 | aJwt() | jwtSimple.js:13:11:13:28 | UserToken |
| jwtSimple.js:20:11:20:28 | UserToken | jwtSimple.js:23:23:23:31 | UserToken |
| jwtSimple.js:20:11:20:28 | UserToken | jwtSimple.js:23:23:23:31 | UserToken |
| jwtSimple.js:20:11:20:28 | UserToken | jwtSimple.js:24:23:24:31 | UserToken |
| jwtSimple.js:20:11:20:28 | UserToken | jwtSimple.js:24:23:24:31 | UserToken |
| jwtSimple.js:20:23:20:28 | aJwt() | jwtSimple.js:20:11:20:28 | UserToken |
| jwtSimple.js:20:23:20:28 | aJwt() | jwtSimple.js:20:11:20:28 | UserToken |
| jwtSimple.js:28:11:28:28 | UserToken | jwtSimple.js:31:23:31:31 | UserToken |
| jwtSimple.js:28:11:28:28 | UserToken | jwtSimple.js:31:23:31:31 | UserToken |
| jwtSimple.js:28:11:28:28 | UserToken | jwtSimple.js:32:23:32:31 | UserToken |
| jwtSimple.js:28:11:28:28 | UserToken | jwtSimple.js:32:23:32:31 | UserToken |
| jwtSimple.js:28:23:28:28 | aJwt() | jwtSimple.js:28:11:28:28 | UserToken |
| jwtSimple.js:28:23:28:28 | aJwt() | jwtSimple.js:28:11:28:28 | UserToken |
#select
| JsonWebToken.js:13:23:13:28 | aJwt() | JsonWebToken.js:13:23:13:28 | aJwt() | JsonWebToken.js:16:28:16:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:16:28:16:36 | UserToken | without signature verification |
| JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:23:28:23:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:23:28:23:36 | UserToken | without signature verification |
| JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:20:23:20:28 | aJwt() | JsonWebToken.js:24:28:24:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:24:28:24:36 | UserToken | without signature verification |
| jose.js:12:23:12:28 | aJwt() | jose.js:12:23:12:28 | aJwt() | jose.js:15:20:15:28 | UserToken | Decoding JWT $@. | jose.js:15:20:15:28 | UserToken | without signature verification |
| jwtDecode.js:13:23:13:28 | aJwt() | jwtDecode.js:13:23:13:28 | aJwt() | jwtDecode.js:17:16:17:24 | UserToken | Decoding JWT $@. | jwtDecode.js:17:16:17:24 | UserToken | without signature verification |
| jwtSimple.js:13:23:13:28 | aJwt() | jwtSimple.js:13:23:13:28 | aJwt() | jwtSimple.js:16:23:16:31 | UserToken | Decoding JWT $@. | jwtSimple.js:16:23:16:31 | UserToken | without signature verification |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-347/decodeJwtWithoutVerificationLocalSource.ql

View File

@@ -0,0 +1,31 @@
const jose = require('jose')
function getSecret() {
return "A Safe generated random key"
}
function aJWT() {
return "A JWT provided by user"
}
(function () {
const UserToken = aJwt()
// no signature verification
jose.decodeJwt(UserToken) // NOT OK
})();
(async function () {
const UserToken = aJwt()
// first without signature verification then with signature verification for same UserToken
jose.decodeJwt(UserToken) // OK
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret())) // OK
})();
(async function () {
const UserToken = aJwt()
// with signature verification
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret())) // OK
})();

View File

@@ -0,0 +1,18 @@
const express = require('express')
const jwt_decode = require('jwt-decode');
function getSecret() {
return "A Safe generated random key"
}
function aJWT() {
return "A JWT provided by user"
}
(function () {
const UserToken = aJwt()
// jwt-decode
// no signature verification
jwt_decode(UserToken) // NOT OK
})();

View File

@@ -0,0 +1,33 @@
const express = require('express')
const jwt_simple = require('jwt-simple');
function getSecret() {
return "A Safe generated random key"
}
function aJWT() {
return "A JWT provided by user"
}
(function () {
const UserToken = aJwt()
// BAD: no signature verification
jwt_simple.decode(UserToken, getSecret(), true); // NOT OK
})();
(function () {
const UserToken = aJwt()
// GOOD: all with with signature verification
jwt_simple.decode(UserToken, getSecret(), false); // OK
jwt_simple.decode(UserToken, getSecret()); // OK
})();
(function () {
const UserToken = aJwt()
// GOOD: first without signature verification then with signature verification for same UserToken
jwt_simple.decode(UserToken, getSecret(), true); // OK
jwt_simple.decode(UserToken, getSecret()); // OK
})();

View File

@@ -0,0 +1,49 @@
const express = require('express')
const app = express()
const jwtJsonwebtoken = require('jsonwebtoken');
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jwtJsonwebtoken1', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken) // NOT OK
})
app.get('/jwtJsonwebtoken2', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken) // NOT OK
jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // NOT OK
})
app.get('/jwtJsonwebtoken3', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
jwtJsonwebtoken.verify(UserToken, getSecret()) // OK
})
app.get('/jwtJsonwebtoken4', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken) // OK
jwtJsonwebtoken.verify(UserToken, getSecret()) // OK
})
app.get('/jwtJsonwebtoken5', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken) // OK
jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256"] }) // OK
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -0,0 +1,161 @@
nodes
| JsonWebToken.js:10:11:10:47 | UserToken |
| JsonWebToken.js:10:23:10:47 | req.hea ... ization |
| JsonWebToken.js:10:23:10:47 | req.hea ... ization |
| JsonWebToken.js:13:28:13:36 | UserToken |
| JsonWebToken.js:13:28:13:36 | UserToken |
| JsonWebToken.js:17:11:17:47 | UserToken |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization |
| JsonWebToken.js:20:28:20:36 | UserToken |
| JsonWebToken.js:20:28:20:36 | UserToken |
| JsonWebToken.js:21:28:21:36 | UserToken |
| JsonWebToken.js:21:28:21:36 | UserToken |
| JsonWebToken.js:25:11:25:47 | UserToken |
| JsonWebToken.js:25:23:25:47 | req.hea ... ization |
| JsonWebToken.js:25:23:25:47 | req.hea ... ization |
| JsonWebToken.js:28:28:28:36 | UserToken |
| JsonWebToken.js:28:28:28:36 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization |
| JsonWebToken.js:35:28:35:36 | UserToken |
| JsonWebToken.js:35:28:35:36 | UserToken |
| JsonWebToken.js:36:28:36:36 | UserToken |
| JsonWebToken.js:36:28:36:36 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization |
| JsonWebToken.js:43:28:43:36 | UserToken |
| JsonWebToken.js:43:28:43:36 | UserToken |
| JsonWebToken.js:44:28:44:36 | UserToken |
| JsonWebToken.js:44:28:44:36 | UserToken |
| jose.js:11:11:11:47 | UserToken |
| jose.js:11:23:11:47 | req.hea ... ization |
| jose.js:11:23:11:47 | req.hea ... ization |
| jose.js:13:20:13:28 | UserToken |
| jose.js:13:20:13:28 | UserToken |
| jose.js:18:11:18:47 | UserToken |
| jose.js:18:23:18:47 | req.hea ... ization |
| jose.js:18:23:18:47 | req.hea ... ization |
| jose.js:20:26:20:34 | UserToken |
| jose.js:20:26:20:34 | UserToken |
| jose.js:24:11:24:47 | UserToken |
| jose.js:24:11:24:47 | UserToken |
| jose.js:24:23:24:47 | req.hea ... ization |
| jose.js:24:23:24:47 | req.hea ... ization |
| jose.js:24:23:24:47 | req.hea ... ization |
| jose.js:24:23:24:47 | req.hea ... ization |
| jose.js:26:20:26:28 | UserToken |
| jose.js:26:20:26:28 | UserToken |
| jose.js:27:26:27:34 | UserToken |
| jose.js:27:26:27:34 | UserToken |
| jwtDecode.js:11:11:11:47 | UserToken |
| jwtDecode.js:11:23:11:47 | req.hea ... ization |
| jwtDecode.js:11:23:11:47 | req.hea ... ization |
| jwtDecode.js:15:16:15:24 | UserToken |
| jwtDecode.js:15:16:15:24 | UserToken |
| jwtSimple.js:10:11:10:47 | UserToken |
| jwtSimple.js:10:23:10:47 | req.hea ... ization |
| jwtSimple.js:10:23:10:47 | req.hea ... ization |
| jwtSimple.js:13:23:13:31 | UserToken |
| jwtSimple.js:13:23:13:31 | UserToken |
| jwtSimple.js:17:11:17:47 | UserToken |
| jwtSimple.js:17:23:17:47 | req.hea ... ization |
| jwtSimple.js:17:23:17:47 | req.hea ... ization |
| jwtSimple.js:20:23:20:31 | UserToken |
| jwtSimple.js:20:23:20:31 | UserToken |
| jwtSimple.js:21:23:21:31 | UserToken |
| jwtSimple.js:21:23:21:31 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken |
| jwtSimple.js:25:23:25:47 | req.hea ... ization |
| jwtSimple.js:25:23:25:47 | req.hea ... ization |
| jwtSimple.js:25:23:25:47 | req.hea ... ization |
| jwtSimple.js:25:23:25:47 | req.hea ... ization |
| jwtSimple.js:28:23:28:31 | UserToken |
| jwtSimple.js:28:23:28:31 | UserToken |
| jwtSimple.js:29:23:29:31 | UserToken |
| jwtSimple.js:29:23:29:31 | UserToken |
edges
| JsonWebToken.js:10:11:10:47 | UserToken | JsonWebToken.js:13:28:13:36 | UserToken |
| JsonWebToken.js:10:11:10:47 | UserToken | JsonWebToken.js:13:28:13:36 | UserToken |
| JsonWebToken.js:10:23:10:47 | req.hea ... ization | JsonWebToken.js:10:11:10:47 | UserToken |
| JsonWebToken.js:10:23:10:47 | req.hea ... ization | JsonWebToken.js:10:11:10:47 | UserToken |
| JsonWebToken.js:17:11:17:47 | UserToken | JsonWebToken.js:20:28:20:36 | UserToken |
| JsonWebToken.js:17:11:17:47 | UserToken | JsonWebToken.js:20:28:20:36 | UserToken |
| JsonWebToken.js:17:11:17:47 | UserToken | JsonWebToken.js:21:28:21:36 | UserToken |
| JsonWebToken.js:17:11:17:47 | UserToken | JsonWebToken.js:21:28:21:36 | UserToken |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:17:11:17:47 | UserToken |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:17:11:17:47 | UserToken |
| JsonWebToken.js:25:11:25:47 | UserToken | JsonWebToken.js:28:28:28:36 | UserToken |
| JsonWebToken.js:25:11:25:47 | UserToken | JsonWebToken.js:28:28:28:36 | UserToken |
| JsonWebToken.js:25:23:25:47 | req.hea ... ization | JsonWebToken.js:25:11:25:47 | UserToken |
| JsonWebToken.js:25:23:25:47 | req.hea ... ization | JsonWebToken.js:25:11:25:47 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken | JsonWebToken.js:35:28:35:36 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken | JsonWebToken.js:35:28:35:36 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken | JsonWebToken.js:36:28:36:36 | UserToken |
| JsonWebToken.js:32:11:32:47 | UserToken | JsonWebToken.js:36:28:36:36 | UserToken |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization | JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization | JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization | JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:32:23:32:47 | req.hea ... ization | JsonWebToken.js:32:11:32:47 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken | JsonWebToken.js:43:28:43:36 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken | JsonWebToken.js:43:28:43:36 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken | JsonWebToken.js:44:28:44:36 | UserToken |
| JsonWebToken.js:40:11:40:47 | UserToken | JsonWebToken.js:44:28:44:36 | UserToken |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization | JsonWebToken.js:40:11:40:47 | UserToken |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization | JsonWebToken.js:40:11:40:47 | UserToken |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization | JsonWebToken.js:40:11:40:47 | UserToken |
| JsonWebToken.js:40:23:40:47 | req.hea ... ization | JsonWebToken.js:40:11:40:47 | UserToken |
| jose.js:11:11:11:47 | UserToken | jose.js:13:20:13:28 | UserToken |
| jose.js:11:11:11:47 | UserToken | jose.js:13:20:13:28 | UserToken |
| jose.js:11:23:11:47 | req.hea ... ization | jose.js:11:11:11:47 | UserToken |
| jose.js:11:23:11:47 | req.hea ... ization | jose.js:11:11:11:47 | UserToken |
| jose.js:18:11:18:47 | UserToken | jose.js:20:26:20:34 | UserToken |
| jose.js:18:11:18:47 | UserToken | jose.js:20:26:20:34 | UserToken |
| jose.js:18:23:18:47 | req.hea ... ization | jose.js:18:11:18:47 | UserToken |
| jose.js:18:23:18:47 | req.hea ... ization | jose.js:18:11:18:47 | UserToken |
| jose.js:24:11:24:47 | UserToken | jose.js:26:20:26:28 | UserToken |
| jose.js:24:11:24:47 | UserToken | jose.js:26:20:26:28 | UserToken |
| jose.js:24:11:24:47 | UserToken | jose.js:27:26:27:34 | UserToken |
| jose.js:24:11:24:47 | UserToken | jose.js:27:26:27:34 | UserToken |
| jose.js:24:23:24:47 | req.hea ... ization | jose.js:24:11:24:47 | UserToken |
| jose.js:24:23:24:47 | req.hea ... ization | jose.js:24:11:24:47 | UserToken |
| jose.js:24:23:24:47 | req.hea ... ization | jose.js:24:11:24:47 | UserToken |
| jose.js:24:23:24:47 | req.hea ... ization | jose.js:24:11:24:47 | UserToken |
| jwtDecode.js:11:11:11:47 | UserToken | jwtDecode.js:15:16:15:24 | UserToken |
| jwtDecode.js:11:11:11:47 | UserToken | jwtDecode.js:15:16:15:24 | UserToken |
| jwtDecode.js:11:23:11:47 | req.hea ... ization | jwtDecode.js:11:11:11:47 | UserToken |
| jwtDecode.js:11:23:11:47 | req.hea ... ization | jwtDecode.js:11:11:11:47 | UserToken |
| jwtSimple.js:10:11:10:47 | UserToken | jwtSimple.js:13:23:13:31 | UserToken |
| jwtSimple.js:10:11:10:47 | UserToken | jwtSimple.js:13:23:13:31 | UserToken |
| jwtSimple.js:10:23:10:47 | req.hea ... ization | jwtSimple.js:10:11:10:47 | UserToken |
| jwtSimple.js:10:23:10:47 | req.hea ... ization | jwtSimple.js:10:11:10:47 | UserToken |
| jwtSimple.js:17:11:17:47 | UserToken | jwtSimple.js:20:23:20:31 | UserToken |
| jwtSimple.js:17:11:17:47 | UserToken | jwtSimple.js:20:23:20:31 | UserToken |
| jwtSimple.js:17:11:17:47 | UserToken | jwtSimple.js:21:23:21:31 | UserToken |
| jwtSimple.js:17:11:17:47 | UserToken | jwtSimple.js:21:23:21:31 | UserToken |
| jwtSimple.js:17:23:17:47 | req.hea ... ization | jwtSimple.js:17:11:17:47 | UserToken |
| jwtSimple.js:17:23:17:47 | req.hea ... ization | jwtSimple.js:17:11:17:47 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken | jwtSimple.js:28:23:28:31 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken | jwtSimple.js:28:23:28:31 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken | jwtSimple.js:29:23:29:31 | UserToken |
| jwtSimple.js:25:11:25:47 | UserToken | jwtSimple.js:29:23:29:31 | UserToken |
| jwtSimple.js:25:23:25:47 | req.hea ... ization | jwtSimple.js:25:11:25:47 | UserToken |
| jwtSimple.js:25:23:25:47 | req.hea ... ization | jwtSimple.js:25:11:25:47 | UserToken |
| jwtSimple.js:25:23:25:47 | req.hea ... ization | jwtSimple.js:25:11:25:47 | UserToken |
| jwtSimple.js:25:23:25:47 | req.hea ... ization | jwtSimple.js:25:11:25:47 | UserToken |
#select
| JsonWebToken.js:10:23:10:47 | req.hea ... ization | JsonWebToken.js:10:23:10:47 | req.hea ... ization | JsonWebToken.js:13:28:13:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:13:28:13:36 | UserToken | without signature verification |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:20:28:20:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:20:28:20:36 | UserToken | without signature verification |
| JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:17:23:17:47 | req.hea ... ization | JsonWebToken.js:21:28:21:36 | UserToken | Decoding JWT $@. | JsonWebToken.js:21:28:21:36 | UserToken | without signature verification |
| jose.js:11:23:11:47 | req.hea ... ization | jose.js:11:23:11:47 | req.hea ... ization | jose.js:13:20:13:28 | UserToken | Decoding JWT $@. | jose.js:13:20:13:28 | UserToken | without signature verification |
| jwtDecode.js:11:23:11:47 | req.hea ... ization | jwtDecode.js:11:23:11:47 | req.hea ... ization | jwtDecode.js:15:16:15:24 | UserToken | Decoding JWT $@. | jwtDecode.js:15:16:15:24 | UserToken | without signature verification |
| jwtSimple.js:10:23:10:47 | req.hea ... ization | jwtSimple.js:10:23:10:47 | req.hea ... ization | jwtSimple.js:13:23:13:31 | UserToken | Decoding JWT $@. | jwtSimple.js:13:23:13:31 | UserToken | without signature verification |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-347/decodeJwtWithoutVerification.ql

View File

@@ -0,0 +1,32 @@
const express = require('express')
const app = express()
const jose = require('jose')
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jose1', (req, res) => {
const UserToken = req.headers.authorization;
// no signature verification
jose.decodeJwt(UserToken) // NOT OK
})
app.get('/jose2', async (req, res) => {
const UserToken = req.headers.authorization;
// with signature verification
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret())) // OK
})
app.get('/jose3', async (req, res) => {
const UserToken = req.headers.authorization;
// first without signature verification then with signature verification for same UserToken
jose.decodeJwt(UserToken) // OK
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret())) // OK
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -0,0 +1,20 @@
const express = require('express')
const app = express()
const jwt_decode = require('jwt-decode');
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jwtDecode', (req, res) => {
const UserToken = req.headers.authorization;
// jwt-decode
// no signature verification
jwt_decode(UserToken) // NOT OK
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -0,0 +1,34 @@
const express = require('express')
const app = express()
const jwt_simple = require('jwt-simple');
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jwtSimple1', (req, res) => {
const UserToken = req.headers.authorization;
// no signature verification
jwt_simple.decode(UserToken, getSecret(), true); // NOT OK
})
app.get('/jwtSimple2', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: all with with signature verification
jwt_simple.decode(UserToken, getSecret(), false); // OK
jwt_simple.decode(UserToken, getSecret()); // OK
})
app.get('/jwtSimple3', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwt_simple.decode(UserToken, getSecret(), true); // OK
jwt_simple.decode(UserToken, getSecret()); // OK
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

View File

@@ -5,20 +5,22 @@ flow
| arrays.js:2:16:2:23 | "source" | arrays.js:15:27:15:27 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:16:23:16:23 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:20:8:20:16 | arr.pop() |
| arrays.js:2:16:2:23 | "source" | arrays.js:52:10:52:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:56:10:56:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:60:10:60:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:66:10:66:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:71:10:71:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:74:8:74:29 | arr.fin ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:77:8:77:35 | arrayFi ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:81:10:81:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:84:8:84:17 | arr.at(-1) |
| arrays.js:2:16:2:23 | "source" | arrays.js:39:8:39:24 | arr4_spread.pop() |
| arrays.js:2:16:2:23 | "source" | arrays.js:61:10:61:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:65:10:65:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:69:10:69:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:75:10:75:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:80:10:80:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:83:8:83:29 | arr.fin ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:86:8:86:35 | arrayFi ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:90:10:90:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:93:8:93:17 | arr.at(-1) |
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:30:8:30:17 | arr4.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:33:8:33:17 | arr5.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:35:8:35:26 | arr5.slice(2).pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:41:8:41:17 | arr6.pop() |
| arrays.js:44:4:44:11 | "source" | arrays.js:45:10:45:18 | ary.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:42:8:42:17 | arr5.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:44:8:44:26 | arr5.slice(2).pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:50:8:50:17 | arr6.pop() |
| arrays.js:33:37:33:44 | "source" | arrays.js:35:8:35:25 | arr4_variant.pop() |
| arrays.js:53:4:53:11 | "source" | arrays.js:54:10:54:18 | ary.pop() |

View File

@@ -5,24 +5,26 @@ flow
| arrays.js:2:16:2:23 | "source" | arrays.js:15:27:15:27 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:16:23:16:23 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:20:8:20:16 | arr.pop() |
| arrays.js:2:16:2:23 | "source" | arrays.js:49:8:49:13 | arr[0] |
| arrays.js:2:16:2:23 | "source" | arrays.js:52:10:52:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:56:10:56:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:60:10:60:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:66:10:66:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:71:10:71:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:74:8:74:29 | arr.fin ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:77:8:77:35 | arrayFi ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:81:10:81:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:84:8:84:17 | arr.at(-1) |
| arrays.js:2:16:2:23 | "source" | arrays.js:39:8:39:24 | arr4_spread.pop() |
| arrays.js:2:16:2:23 | "source" | arrays.js:58:8:58:13 | arr[0] |
| arrays.js:2:16:2:23 | "source" | arrays.js:61:10:61:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:65:10:65:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:69:10:69:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:75:10:75:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:80:10:80:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:83:8:83:29 | arr.fin ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:86:8:86:35 | arrayFi ... llback) |
| arrays.js:2:16:2:23 | "source" | arrays.js:90:10:90:10 | x |
| arrays.js:2:16:2:23 | "source" | arrays.js:93:8:93:17 | arr.at(-1) |
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:30:8:30:17 | arr4.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:33:8:33:17 | arr5.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:35:8:35:26 | arr5.slice(2).pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:41:8:41:17 | arr6.pop() |
| arrays.js:44:4:44:11 | "source" | arrays.js:45:10:45:18 | ary.pop() |
| arrays.js:44:4:44:11 | "source" | arrays.js:46:10:46:12 | ary |
| arrays.js:86:9:86:16 | "source" | arrays.js:86:8:86:34 | ["sourc ... ) => x) |
| arrays.js:87:9:87:16 | "source" | arrays.js:87:8:87:36 | ["sourc ... => !!x) |
| arrays.js:29:21:29:28 | "source" | arrays.js:42:8:42:17 | arr5.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:44:8:44:26 | arr5.slice(2).pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:50:8:50:17 | arr6.pop() |
| arrays.js:33:37:33:44 | "source" | arrays.js:35:8:35:25 | arr4_variant.pop() |
| arrays.js:53:4:53:11 | "source" | arrays.js:54:10:54:18 | ary.pop() |
| arrays.js:53:4:53:11 | "source" | arrays.js:55:10:55:12 | ary |
| arrays.js:95:9:95:16 | "source" | arrays.js:95:8:95:34 | ["sourc ... ) => x) |
| arrays.js:96:9:96:16 | "source" | arrays.js:96:8:96:36 | ["sourc ... => !!x) |

View File

@@ -29,6 +29,15 @@
arr4.splice(0, 0, "source");
sink(arr4.pop()); // NOT OK
var arr4_variant = [];
arr4_variant.splice(0, 0, "safe", "source");
arr4_variant.pop();
sink(arr4_variant.pop()); // NOT OK
var arr4_spread = [];
arr4_spread.splice(0, 0, ...arr);
sink(arr4_spread.pop()); // NOT OK
var arr5 = [].concat(arr4);
sink(arr5.pop()); // NOT OK
@@ -46,7 +55,7 @@
sink(ary); // OK - its the array itself, not an element.
});
sink(arr[0]); // OK - tuple like usage.
sink(arr[0]); // OK - tuple like usage.
for (const x of arr) {
sink(x); // NOT OK
@@ -59,7 +68,7 @@
for (const x of [...arr]) {
sink(x); // NOT OK
}
var arr7 = [];
arr7.push(...arr);
for (const x of arr7) {

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ typeTracking
| tst.js:2:16:2:23 | source() | tst.js:37:14:37:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:41:14:41:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:45:14:45:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:49:14:49:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:53:8:53:21 | map.get("key") |
| tst.js:2:16:2:23 | source() | tst.js:59:8:59:22 | map2.get("foo") |
| tst.js:2:16:2:23 | source() | tst.js:64:8:64:26 | map3.get(unknown()) |

View File

@@ -47,7 +47,7 @@
}
for (const e of Array.from(set)) {
sink(e); // NOT OK (not caught by type-tracking, as it doesn't include array steps).
sink(e); // NOT OK
}
sink(map.get("key")); // NOT OK.

View File

@@ -52,9 +52,14 @@ test_FileSystemAccess
| tst.js:56:1:56:18 | shelljs.uniq(file) |
| tst.js:57:1:57:26 | shelljs ... file2) |
| tst.js:58:1:58:32 | shelljs ... file2) |
| tst.js:60:1:60:17 | shelljs.cat(file) |
| tst.js:60:1:60:41 | shelljs ... cement) |
| tst.js:61:1:61:17 | shelljs.cat(file) |
test_MissingFileSystemAccess
test_SystemCommandExecution
| tst.js:14:1:14:27 | shelljs ... ts, cb) |
| tst.js:60:1:60:51 | shelljs ... ec(cmd) |
| tst.js:61:1:61:27 | shelljs ... ec(cmd) |
test_FileNameSource
| tst.js:15:1:15:26 | shelljs ... file2) |
| tst.js:24:1:24:16 | shelljs.ls(file) |

View File

@@ -56,3 +56,6 @@ shelljs.touch(file1, file2);
shelljs.uniq(file);
shelljs.uniq(file1, file2);
shelljs.uniq(opts, file1, file2);
shelljs.cat(file).sed(regex, replacement).exec(cmd);
shelljs.cat(file).exec(cmd);

View File

@@ -80,6 +80,7 @@ taintFlow
| test.js:269:10:269:31 | this.ba ... ource() | test.js:269:10:269:31 | this.ba ... ource() |
| test.js:272:6:272:40 | new MyS ... ource() | test.js:272:6:272:40 | new MyS ... ource() |
| test.js:274:6:274:39 | testlib ... eName() | test.js:274:6:274:39 | testlib ... eName() |
| test.js:277:8:277:31 | "danger ... .danger | test.js:277:8:277:31 | "danger ... .danger |
isSink
| test.js:54:18:54:25 | source() | test-sink |
| test.js:55:22:55:29 | source() | test-sink |

View File

@@ -11,6 +11,7 @@ extensions:
- ['testlib', 'Member[ParamDecoratorSource].DecoratedParameter', 'test-source']
- ['testlib', 'Member[getSource].ReturnValue', 'test-source']
- ['(testlib)', 'Member[parenthesizedPackageName].ReturnValue', 'test-source']
- ['danger-constant', 'Member[danger]', 'test-source']
- addsTo:
pack: codeql/javascript-all

View File

@@ -272,3 +272,9 @@ class MySubclass2 extends MySubclass {
sink(new MySubclass2().baseclassSource()); // NOT OK
sink(testlib.parenthesizedPackageName()); // NOT OK
function dangerConstant() {
sink("danger-constant".danger); // NOT OK
sink("danger-constant".safe); // OK
sink("danger-constant"); // OK
}

View File

@@ -2,6 +2,15 @@ import javascript
import testUtilities.ConsistencyChecking
import semmle.javascript.frameworks.data.internal.ApiGraphModels as ApiGraphModels
class TypeModelFromCodeQL extends ModelInput::TypeModel {
override predicate isTypeUsed(string type) { type = "danger-constant" }
override DataFlow::Node getASource(string type) {
type = "danger-constant" and
result.getStringValue() = "danger-constant"
}
}
module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.(DataFlow::CallNode).getCalleeName() = "source"

View File

@@ -0,0 +1,391 @@
nodes
| adm-zip.js:13:13:13:21 | req.files |
| adm-zip.js:13:13:13:21 | req.files |
| adm-zip.js:13:13:13:33 | req.fil ... ombFile |
| adm-zip.js:17:18:17:24 | tarFile |
| adm-zip.js:24:22:24:28 | tarFile |
| adm-zip.js:24:22:24:33 | tarFile.data |
| adm-zip.js:28:25:28:42 | zipEntry.getData() |
| adm-zip.js:28:25:28:42 | zipEntry.getData() |
| adm-zip.js:32:17:32:41 | admZip. ... "10GB") |
| adm-zip.js:32:17:32:41 | admZip. ... "10GB") |
| adm-zip.js:34:5:34:55 | admZip. ... , true) |
| adm-zip.js:34:5:34:55 | admZip. ... , true) |
| adm-zip.js:36:5:36:38 | admZip. ... , true) |
| adm-zip.js:36:5:36:38 | admZip. ... , true) |
| decompress.js:11:16:11:33 | req.query.filePath |
| decompress.js:11:16:11:33 | req.query.filePath |
| decompress.js:11:16:11:33 | req.query.filePath |
| jszip.js:12:13:12:21 | req.files |
| jszip.js:12:13:12:21 | req.files |
| jszip.js:12:13:12:33 | req.fil ... ombFile |
| jszip.js:12:13:12:38 | req.fil ... le.data |
| jszip.js:32:18:32:24 | zipFile |
| jszip.js:33:22:33:28 | zipFile |
| jszip.js:33:22:33:33 | zipFile.data |
| jszip.js:33:22:33:33 | zipFile.data |
| node-tar.js:15:13:15:21 | req.files |
| node-tar.js:15:13:15:21 | req.files |
| node-tar.js:15:13:15:33 | req.fil ... ombFile |
| node-tar.js:15:13:15:38 | req.fil ... le.data |
| node-tar.js:19:18:19:24 | tarFile |
| node-tar.js:21:23:21:49 | Readabl ... e.data) |
| node-tar.js:21:37:21:43 | tarFile |
| node-tar.js:21:37:21:48 | tarFile.data |
| node-tar.js:24:9:24:15 | tar.x() |
| node-tar.js:24:9:24:15 | tar.x() |
| node-tar.js:29:5:29:37 | fs.crea ... e.name) |
| node-tar.js:29:25:29:31 | tarFile |
| node-tar.js:29:25:29:36 | tarFile.name |
| node-tar.js:30:9:33:10 | tar.x({ ... }) |
| node-tar.js:30:9:33:10 | tar.x({ ... }) |
| node-tar.js:45:5:45:37 | fs.crea ... e.name) |
| node-tar.js:45:25:45:31 | tarFile |
| node-tar.js:45:25:45:36 | tarFile.name |
| node-tar.js:46:9:46:20 | decompressor |
| node-tar.js:48:9:50:10 | tar.x({ ... }) |
| node-tar.js:48:9:50:10 | tar.x({ ... }) |
| node-tar.js:58:19:58:25 | tarFile |
| node-tar.js:58:19:58:30 | tarFile.name |
| node-tar.js:58:19:58:30 | tarFile.name |
| node-tar.js:59:25:59:31 | tarFile |
| node-tar.js:59:25:59:36 | tarFile.name |
| node-tar.js:59:25:59:36 | tarFile.name |
| pako.js:12:14:12:22 | req.files |
| pako.js:12:14:12:22 | req.files |
| pako.js:12:14:12:34 | req.fil ... ombFile |
| pako.js:12:14:12:39 | req.fil ... le.data |
| pako.js:13:14:13:22 | req.files |
| pako.js:13:14:13:22 | req.files |
| pako.js:13:14:13:34 | req.fil ... ombFile |
| pako.js:13:14:13:39 | req.fil ... le.data |
| pako.js:17:19:17:25 | zipFile |
| pako.js:18:11:18:68 | myArray |
| pako.js:18:21:18:68 | Buffer. ... uffer)) |
| pako.js:18:33:18:67 | new Uin ... buffer) |
| pako.js:18:48:18:54 | zipFile |
| pako.js:18:48:18:59 | zipFile.data |
| pako.js:18:48:18:66 | zipFile.data.buffer |
| pako.js:21:31:21:37 | myArray |
| pako.js:21:31:21:37 | myArray |
| pako.js:28:19:28:25 | zipFile |
| pako.js:29:11:29:62 | myArray |
| pako.js:29:21:29:55 | new Uin ... buffer) |
| pako.js:29:21:29:62 | new Uin ... .buffer |
| pako.js:29:36:29:42 | zipFile |
| pako.js:29:36:29:47 | zipFile.data |
| pako.js:29:36:29:54 | zipFile.data.buffer |
| pako.js:32:31:32:37 | myArray |
| pako.js:32:31:32:37 | myArray |
| unbzip2.js:12:5:12:43 | fs.crea ... lePath) |
| unbzip2.js:12:25:12:42 | req.query.FilePath |
| unbzip2.js:12:25:12:42 | req.query.FilePath |
| unbzip2.js:12:50:12:54 | bz2() |
| unbzip2.js:12:50:12:54 | bz2() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) |
| unzipper.js:13:40:13:48 | req.files |
| unzipper.js:13:40:13:48 | req.files |
| unzipper.js:13:40:13:56 | req.files.ZipFile |
| unzipper.js:13:40:13:61 | req.fil ... le.data |
| unzipper.js:16:23:16:63 | unzippe ... ath' }) |
| unzipper.js:16:23:16:63 | unzippe ... ath' }) |
| unzipper.js:19:23:19:41 | unzipper.ParseOne() |
| unzipper.js:19:23:19:41 | unzipper.ParseOne() |
| unzipper.js:24:15:24:30 | unzipper.Parse() |
| unzipper.js:24:15:24:30 | unzipper.Parse() |
| unzipper.js:34:15:34:30 | unzipper.Parse() |
| unzipper.js:34:15:34:30 | unzipper.Parse() |
| unzipper.js:41:35:41:71 | unzippe ... true }) |
| unzipper.js:41:35:41:71 | unzippe ... true }) |
| unzipper.js:51:36:51:72 | unzippe ... true }) |
| unzipper.js:51:36:51:72 | unzippe ... true }) |
| unzipper.js:60:23:60:38 | unzipper.Parse() |
| unzipper.js:60:23:60:38 | unzipper.Parse() |
| unzipper.js:73:23:73:38 | unzipper.Parse() |
| unzipper.js:73:23:73:38 | unzipper.Parse() |
| yauzl.js:12:18:12:26 | req.files |
| yauzl.js:12:18:12:26 | req.files |
| yauzl.js:12:18:12:34 | req.files.zipFile |
| yauzl.js:12:18:12:39 | req.fil ... le.data |
| yauzl.js:12:18:12:39 | req.fil ... le.data |
| yauzl.js:13:22:13:30 | req.files |
| yauzl.js:13:22:13:30 | req.files |
| yauzl.js:13:22:13:38 | req.files.zipFile |
| yauzl.js:13:22:13:43 | req.fil ... le.data |
| yauzl.js:13:22:13:43 | req.fil ... le.data |
| yauzl.js:14:34:14:42 | req.files |
| yauzl.js:14:34:14:42 | req.files |
| yauzl.js:14:34:14:50 | req.files.zipFile |
| yauzl.js:14:34:14:55 | req.fil ... le.data |
| yauzl.js:14:34:14:55 | req.fil ... le.data |
| yauzl.js:37:16:37:33 | req.query.filePath |
| yauzl.js:37:16:37:33 | req.query.filePath |
| yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:41:64:41:73 | readStream |
| yauzl.js:41:64:41:73 | readStream |
| yauzl.js:43:21:43:39 | zipfile.readEntry() |
| yauzl.js:43:21:43:39 | zipfile.readEntry() |
| zlib.js:15:19:15:27 | req.files |
| zlib.js:15:19:15:27 | req.files |
| zlib.js:15:19:15:39 | req.fil ... ombFile |
| zlib.js:15:19:15:44 | req.fil ... le.data |
| zlib.js:17:18:17:26 | req.files |
| zlib.js:17:18:17:26 | req.files |
| zlib.js:17:18:17:38 | req.fil ... ombFile |
| zlib.js:17:18:17:43 | req.fil ... le.data |
| zlib.js:19:24:19:32 | req.files |
| zlib.js:19:24:19:32 | req.files |
| zlib.js:19:24:19:44 | req.fil ... ombFile |
| zlib.js:19:24:19:49 | req.fil ... le.data |
| zlib.js:21:32:21:40 | req.files |
| zlib.js:21:32:21:40 | req.files |
| zlib.js:21:32:21:52 | req.fil ... ombFile |
| zlib.js:21:32:21:57 | req.fil ... le.data |
| zlib.js:27:24:27:30 | zipFile |
| zlib.js:29:9:29:15 | zipFile |
| zlib.js:29:9:29:20 | zipFile.data |
| zlib.js:29:9:29:20 | zipFile.data |
| zlib.js:33:9:33:15 | zipFile |
| zlib.js:33:9:33:20 | zipFile.data |
| zlib.js:33:9:33:20 | zipFile.data |
| zlib.js:38:9:38:15 | zipFile |
| zlib.js:38:9:38:20 | zipFile.data |
| zlib.js:38:9:38:20 | zipFile.data |
| zlib.js:62:23:62:29 | zipFile |
| zlib.js:63:21:63:27 | zipFile |
| zlib.js:63:21:63:32 | zipFile.data |
| zlib.js:63:21:63:32 | zipFile.data |
| zlib.js:64:20:64:26 | zipFile |
| zlib.js:64:20:64:31 | zipFile.data |
| zlib.js:64:20:64:31 | zipFile.data |
| zlib.js:65:31:65:37 | zipFile |
| zlib.js:65:31:65:42 | zipFile.data |
| zlib.js:65:31:65:42 | zipFile.data |
| zlib.js:74:29:74:35 | zipFile |
| zlib.js:75:25:75:51 | Readabl ... e.data) |
| zlib.js:75:39:75:45 | zipFile |
| zlib.js:75:39:75:50 | zipFile.data |
| zlib.js:77:22:77:40 | zlib.createGunzip() |
| zlib.js:77:22:77:40 | zlib.createGunzip() |
| zlib.js:78:22:78:39 | zlib.createUnzip() |
| zlib.js:78:22:78:39 | zlib.createUnzip() |
| zlib.js:79:22:79:50 | zlib.cr ... press() |
| zlib.js:79:22:79:50 | zlib.cr ... press() |
| zlib.js:82:43:82:49 | zipFile |
| zlib.js:83:11:83:51 | inputStream |
| zlib.js:83:25:83:51 | Readabl ... e.data) |
| zlib.js:83:39:83:45 | zipFile |
| zlib.js:83:39:83:50 | zipFile.data |
| zlib.js:86:9:86:19 | inputStream |
| zlib.js:87:9:87:27 | zlib.createGunzip() |
| zlib.js:87:9:87:27 | zlib.createGunzip() |
edges
| adm-zip.js:13:13:13:21 | req.files | adm-zip.js:13:13:13:33 | req.fil ... ombFile |
| adm-zip.js:13:13:13:21 | req.files | adm-zip.js:13:13:13:33 | req.fil ... ombFile |
| adm-zip.js:13:13:13:33 | req.fil ... ombFile | adm-zip.js:17:18:17:24 | tarFile |
| adm-zip.js:17:18:17:24 | tarFile | adm-zip.js:24:22:24:28 | tarFile |
| adm-zip.js:24:22:24:28 | tarFile | adm-zip.js:24:22:24:33 | tarFile.data |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:28:25:28:42 | zipEntry.getData() |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:28:25:28:42 | zipEntry.getData() |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:32:17:32:41 | admZip. ... "10GB") |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:32:17:32:41 | admZip. ... "10GB") |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:34:5:34:55 | admZip. ... , true) |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:34:5:34:55 | admZip. ... , true) |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:36:5:36:38 | admZip. ... , true) |
| adm-zip.js:24:22:24:33 | tarFile.data | adm-zip.js:36:5:36:38 | admZip. ... , true) |
| decompress.js:11:16:11:33 | req.query.filePath | decompress.js:11:16:11:33 | req.query.filePath |
| jszip.js:12:13:12:21 | req.files | jszip.js:12:13:12:33 | req.fil ... ombFile |
| jszip.js:12:13:12:21 | req.files | jszip.js:12:13:12:33 | req.fil ... ombFile |
| jszip.js:12:13:12:33 | req.fil ... ombFile | jszip.js:12:13:12:38 | req.fil ... le.data |
| jszip.js:12:13:12:38 | req.fil ... le.data | jszip.js:32:18:32:24 | zipFile |
| jszip.js:32:18:32:24 | zipFile | jszip.js:33:22:33:28 | zipFile |
| jszip.js:33:22:33:28 | zipFile | jszip.js:33:22:33:33 | zipFile.data |
| jszip.js:33:22:33:28 | zipFile | jszip.js:33:22:33:33 | zipFile.data |
| node-tar.js:15:13:15:21 | req.files | node-tar.js:15:13:15:33 | req.fil ... ombFile |
| node-tar.js:15:13:15:21 | req.files | node-tar.js:15:13:15:33 | req.fil ... ombFile |
| node-tar.js:15:13:15:33 | req.fil ... ombFile | node-tar.js:15:13:15:38 | req.fil ... le.data |
| node-tar.js:15:13:15:38 | req.fil ... le.data | node-tar.js:19:18:19:24 | tarFile |
| node-tar.js:19:18:19:24 | tarFile | node-tar.js:21:37:21:43 | tarFile |
| node-tar.js:19:18:19:24 | tarFile | node-tar.js:29:25:29:31 | tarFile |
| node-tar.js:19:18:19:24 | tarFile | node-tar.js:45:25:45:31 | tarFile |
| node-tar.js:19:18:19:24 | tarFile | node-tar.js:58:19:58:25 | tarFile |
| node-tar.js:19:18:19:24 | tarFile | node-tar.js:59:25:59:31 | tarFile |
| node-tar.js:21:23:21:49 | Readabl ... e.data) | node-tar.js:24:9:24:15 | tar.x() |
| node-tar.js:21:23:21:49 | Readabl ... e.data) | node-tar.js:24:9:24:15 | tar.x() |
| node-tar.js:21:37:21:43 | tarFile | node-tar.js:21:37:21:48 | tarFile.data |
| node-tar.js:21:37:21:48 | tarFile.data | node-tar.js:21:23:21:49 | Readabl ... e.data) |
| node-tar.js:29:5:29:37 | fs.crea ... e.name) | node-tar.js:30:9:33:10 | tar.x({ ... }) |
| node-tar.js:29:5:29:37 | fs.crea ... e.name) | node-tar.js:30:9:33:10 | tar.x({ ... }) |
| node-tar.js:29:25:29:31 | tarFile | node-tar.js:29:25:29:36 | tarFile.name |
| node-tar.js:29:25:29:36 | tarFile.name | node-tar.js:29:5:29:37 | fs.crea ... e.name) |
| node-tar.js:45:5:45:37 | fs.crea ... e.name) | node-tar.js:46:9:46:20 | decompressor |
| node-tar.js:45:25:45:31 | tarFile | node-tar.js:45:25:45:36 | tarFile.name |
| node-tar.js:45:25:45:36 | tarFile.name | node-tar.js:45:5:45:37 | fs.crea ... e.name) |
| node-tar.js:46:9:46:20 | decompressor | node-tar.js:48:9:50:10 | tar.x({ ... }) |
| node-tar.js:46:9:46:20 | decompressor | node-tar.js:48:9:50:10 | tar.x({ ... }) |
| node-tar.js:58:19:58:25 | tarFile | node-tar.js:58:19:58:30 | tarFile.name |
| node-tar.js:58:19:58:25 | tarFile | node-tar.js:58:19:58:30 | tarFile.name |
| node-tar.js:59:25:59:31 | tarFile | node-tar.js:59:25:59:36 | tarFile.name |
| node-tar.js:59:25:59:31 | tarFile | node-tar.js:59:25:59:36 | tarFile.name |
| pako.js:12:14:12:22 | req.files | pako.js:12:14:12:34 | req.fil ... ombFile |
| pako.js:12:14:12:22 | req.files | pako.js:12:14:12:34 | req.fil ... ombFile |
| pako.js:12:14:12:34 | req.fil ... ombFile | pako.js:12:14:12:39 | req.fil ... le.data |
| pako.js:12:14:12:39 | req.fil ... le.data | pako.js:17:19:17:25 | zipFile |
| pako.js:13:14:13:22 | req.files | pako.js:13:14:13:34 | req.fil ... ombFile |
| pako.js:13:14:13:22 | req.files | pako.js:13:14:13:34 | req.fil ... ombFile |
| pako.js:13:14:13:34 | req.fil ... ombFile | pako.js:13:14:13:39 | req.fil ... le.data |
| pako.js:13:14:13:39 | req.fil ... le.data | pako.js:28:19:28:25 | zipFile |
| pako.js:17:19:17:25 | zipFile | pako.js:18:48:18:54 | zipFile |
| pako.js:18:11:18:68 | myArray | pako.js:21:31:21:37 | myArray |
| pako.js:18:11:18:68 | myArray | pako.js:21:31:21:37 | myArray |
| pako.js:18:21:18:68 | Buffer. ... uffer)) | pako.js:18:11:18:68 | myArray |
| pako.js:18:33:18:67 | new Uin ... buffer) | pako.js:18:21:18:68 | Buffer. ... uffer)) |
| pako.js:18:48:18:54 | zipFile | pako.js:18:48:18:59 | zipFile.data |
| pako.js:18:48:18:59 | zipFile.data | pako.js:18:48:18:66 | zipFile.data.buffer |
| pako.js:18:48:18:66 | zipFile.data.buffer | pako.js:18:33:18:67 | new Uin ... buffer) |
| pako.js:28:19:28:25 | zipFile | pako.js:29:36:29:42 | zipFile |
| pako.js:29:11:29:62 | myArray | pako.js:32:31:32:37 | myArray |
| pako.js:29:11:29:62 | myArray | pako.js:32:31:32:37 | myArray |
| pako.js:29:21:29:55 | new Uin ... buffer) | pako.js:29:21:29:62 | new Uin ... .buffer |
| pako.js:29:21:29:62 | new Uin ... .buffer | pako.js:29:11:29:62 | myArray |
| pako.js:29:36:29:42 | zipFile | pako.js:29:36:29:47 | zipFile.data |
| pako.js:29:36:29:47 | zipFile.data | pako.js:29:36:29:54 | zipFile.data.buffer |
| pako.js:29:36:29:54 | zipFile.data.buffer | pako.js:29:21:29:55 | new Uin ... buffer) |
| unbzip2.js:12:5:12:43 | fs.crea ... lePath) | unbzip2.js:12:50:12:54 | bz2() |
| unbzip2.js:12:5:12:43 | fs.crea ... lePath) | unbzip2.js:12:50:12:54 | bz2() |
| unbzip2.js:12:25:12:42 | req.query.FilePath | unbzip2.js:12:5:12:43 | fs.crea ... lePath) |
| unbzip2.js:12:25:12:42 | req.query.FilePath | unbzip2.js:12:5:12:43 | fs.crea ... lePath) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:16:23:16:63 | unzippe ... ath' }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:16:23:16:63 | unzippe ... ath' }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:19:23:19:41 | unzipper.ParseOne() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:19:23:19:41 | unzipper.ParseOne() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:24:15:24:30 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:24:15:24:30 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:34:15:34:30 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:34:15:34:30 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:41:35:41:71 | unzippe ... true }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:41:35:41:71 | unzippe ... true }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:51:36:51:72 | unzippe ... true }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:51:36:51:72 | unzippe ... true }) |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:60:23:60:38 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:60:23:60:38 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:73:23:73:38 | unzipper.Parse() |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:73:23:73:38 | unzipper.Parse() |
| unzipper.js:13:40:13:48 | req.files | unzipper.js:13:40:13:56 | req.files.ZipFile |
| unzipper.js:13:40:13:48 | req.files | unzipper.js:13:40:13:56 | req.files.ZipFile |
| unzipper.js:13:40:13:56 | req.files.ZipFile | unzipper.js:13:40:13:61 | req.fil ... le.data |
| unzipper.js:13:40:13:61 | req.fil ... le.data | unzipper.js:13:26:13:62 | Readabl ... e.data) |
| yauzl.js:12:18:12:26 | req.files | yauzl.js:12:18:12:34 | req.files.zipFile |
| yauzl.js:12:18:12:26 | req.files | yauzl.js:12:18:12:34 | req.files.zipFile |
| yauzl.js:12:18:12:34 | req.files.zipFile | yauzl.js:12:18:12:39 | req.fil ... le.data |
| yauzl.js:12:18:12:34 | req.files.zipFile | yauzl.js:12:18:12:39 | req.fil ... le.data |
| yauzl.js:13:22:13:30 | req.files | yauzl.js:13:22:13:38 | req.files.zipFile |
| yauzl.js:13:22:13:30 | req.files | yauzl.js:13:22:13:38 | req.files.zipFile |
| yauzl.js:13:22:13:38 | req.files.zipFile | yauzl.js:13:22:13:43 | req.fil ... le.data |
| yauzl.js:13:22:13:38 | req.files.zipFile | yauzl.js:13:22:13:43 | req.fil ... le.data |
| yauzl.js:14:34:14:42 | req.files | yauzl.js:14:34:14:50 | req.files.zipFile |
| yauzl.js:14:34:14:42 | req.files | yauzl.js:14:34:14:50 | req.files.zipFile |
| yauzl.js:14:34:14:50 | req.files.zipFile | yauzl.js:14:34:14:55 | req.fil ... le.data |
| yauzl.js:14:34:14:50 | req.files.zipFile | yauzl.js:14:34:14:55 | req.fil ... le.data |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:39:9:39:27 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:41:64:41:73 | readStream |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:41:64:41:73 | readStream |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:41:64:41:73 | readStream |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:41:64:41:73 | readStream |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:43:21:43:39 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:43:21:43:39 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:43:21:43:39 | zipfile.readEntry() |
| yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:43:21:43:39 | zipfile.readEntry() |
| zlib.js:15:19:15:27 | req.files | zlib.js:15:19:15:39 | req.fil ... ombFile |
| zlib.js:15:19:15:27 | req.files | zlib.js:15:19:15:39 | req.fil ... ombFile |
| zlib.js:15:19:15:39 | req.fil ... ombFile | zlib.js:15:19:15:44 | req.fil ... le.data |
| zlib.js:15:19:15:44 | req.fil ... le.data | zlib.js:27:24:27:30 | zipFile |
| zlib.js:17:18:17:26 | req.files | zlib.js:17:18:17:38 | req.fil ... ombFile |
| zlib.js:17:18:17:26 | req.files | zlib.js:17:18:17:38 | req.fil ... ombFile |
| zlib.js:17:18:17:38 | req.fil ... ombFile | zlib.js:17:18:17:43 | req.fil ... le.data |
| zlib.js:17:18:17:43 | req.fil ... le.data | zlib.js:62:23:62:29 | zipFile |
| zlib.js:19:24:19:32 | req.files | zlib.js:19:24:19:44 | req.fil ... ombFile |
| zlib.js:19:24:19:32 | req.files | zlib.js:19:24:19:44 | req.fil ... ombFile |
| zlib.js:19:24:19:44 | req.fil ... ombFile | zlib.js:19:24:19:49 | req.fil ... le.data |
| zlib.js:19:24:19:49 | req.fil ... le.data | zlib.js:74:29:74:35 | zipFile |
| zlib.js:21:32:21:40 | req.files | zlib.js:21:32:21:52 | req.fil ... ombFile |
| zlib.js:21:32:21:40 | req.files | zlib.js:21:32:21:52 | req.fil ... ombFile |
| zlib.js:21:32:21:52 | req.fil ... ombFile | zlib.js:21:32:21:57 | req.fil ... le.data |
| zlib.js:21:32:21:57 | req.fil ... le.data | zlib.js:82:43:82:49 | zipFile |
| zlib.js:27:24:27:30 | zipFile | zlib.js:29:9:29:15 | zipFile |
| zlib.js:27:24:27:30 | zipFile | zlib.js:33:9:33:15 | zipFile |
| zlib.js:27:24:27:30 | zipFile | zlib.js:38:9:38:15 | zipFile |
| zlib.js:29:9:29:15 | zipFile | zlib.js:29:9:29:20 | zipFile.data |
| zlib.js:29:9:29:15 | zipFile | zlib.js:29:9:29:20 | zipFile.data |
| zlib.js:33:9:33:15 | zipFile | zlib.js:33:9:33:20 | zipFile.data |
| zlib.js:33:9:33:15 | zipFile | zlib.js:33:9:33:20 | zipFile.data |
| zlib.js:38:9:38:15 | zipFile | zlib.js:38:9:38:20 | zipFile.data |
| zlib.js:38:9:38:15 | zipFile | zlib.js:38:9:38:20 | zipFile.data |
| zlib.js:62:23:62:29 | zipFile | zlib.js:63:21:63:27 | zipFile |
| zlib.js:62:23:62:29 | zipFile | zlib.js:64:20:64:26 | zipFile |
| zlib.js:62:23:62:29 | zipFile | zlib.js:65:31:65:37 | zipFile |
| zlib.js:63:21:63:27 | zipFile | zlib.js:63:21:63:32 | zipFile.data |
| zlib.js:63:21:63:27 | zipFile | zlib.js:63:21:63:32 | zipFile.data |
| zlib.js:64:20:64:26 | zipFile | zlib.js:64:20:64:31 | zipFile.data |
| zlib.js:64:20:64:26 | zipFile | zlib.js:64:20:64:31 | zipFile.data |
| zlib.js:65:31:65:37 | zipFile | zlib.js:65:31:65:42 | zipFile.data |
| zlib.js:65:31:65:37 | zipFile | zlib.js:65:31:65:42 | zipFile.data |
| zlib.js:74:29:74:35 | zipFile | zlib.js:75:39:75:45 | zipFile |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:77:22:77:40 | zlib.createGunzip() |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:77:22:77:40 | zlib.createGunzip() |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:78:22:78:39 | zlib.createUnzip() |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:78:22:78:39 | zlib.createUnzip() |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:79:22:79:50 | zlib.cr ... press() |
| zlib.js:75:25:75:51 | Readabl ... e.data) | zlib.js:79:22:79:50 | zlib.cr ... press() |
| zlib.js:75:39:75:45 | zipFile | zlib.js:75:39:75:50 | zipFile.data |
| zlib.js:75:39:75:50 | zipFile.data | zlib.js:75:25:75:51 | Readabl ... e.data) |
| zlib.js:82:43:82:49 | zipFile | zlib.js:83:39:83:45 | zipFile |
| zlib.js:83:11:83:51 | inputStream | zlib.js:86:9:86:19 | inputStream |
| zlib.js:83:25:83:51 | Readabl ... e.data) | zlib.js:83:11:83:51 | inputStream |
| zlib.js:83:39:83:45 | zipFile | zlib.js:83:39:83:50 | zipFile.data |
| zlib.js:83:39:83:50 | zipFile.data | zlib.js:83:25:83:51 | Readabl ... e.data) |
| zlib.js:86:9:86:19 | inputStream | zlib.js:87:9:87:27 | zlib.createGunzip() |
| zlib.js:86:9:86:19 | inputStream | zlib.js:87:9:87:27 | zlib.createGunzip() |
#select
| adm-zip.js:28:25:28:42 | zipEntry.getData() | adm-zip.js:13:13:13:21 | req.files | adm-zip.js:28:25:28:42 | zipEntry.getData() | This Decompression depends on a $@. | adm-zip.js:13:13:13:21 | req.files | potentially untrusted source |
| adm-zip.js:32:17:32:41 | admZip. ... "10GB") | adm-zip.js:13:13:13:21 | req.files | adm-zip.js:32:17:32:41 | admZip. ... "10GB") | This Decompression depends on a $@. | adm-zip.js:13:13:13:21 | req.files | potentially untrusted source |
| adm-zip.js:34:5:34:55 | admZip. ... , true) | adm-zip.js:13:13:13:21 | req.files | adm-zip.js:34:5:34:55 | admZip. ... , true) | This Decompression depends on a $@. | adm-zip.js:13:13:13:21 | req.files | potentially untrusted source |
| adm-zip.js:36:5:36:38 | admZip. ... , true) | adm-zip.js:13:13:13:21 | req.files | adm-zip.js:36:5:36:38 | admZip. ... , true) | This Decompression depends on a $@. | adm-zip.js:13:13:13:21 | req.files | potentially untrusted source |
| decompress.js:11:16:11:33 | req.query.filePath | decompress.js:11:16:11:33 | req.query.filePath | decompress.js:11:16:11:33 | req.query.filePath | This Decompression depends on a $@. | decompress.js:11:16:11:33 | req.query.filePath | potentially untrusted source |
| jszip.js:33:22:33:33 | zipFile.data | jszip.js:12:13:12:21 | req.files | jszip.js:33:22:33:33 | zipFile.data | This Decompression depends on a $@. | jszip.js:12:13:12:21 | req.files | potentially untrusted source |
| node-tar.js:24:9:24:15 | tar.x() | node-tar.js:15:13:15:21 | req.files | node-tar.js:24:9:24:15 | tar.x() | This Decompression depends on a $@. | node-tar.js:15:13:15:21 | req.files | potentially untrusted source |
| node-tar.js:30:9:33:10 | tar.x({ ... }) | node-tar.js:15:13:15:21 | req.files | node-tar.js:30:9:33:10 | tar.x({ ... }) | This Decompression depends on a $@. | node-tar.js:15:13:15:21 | req.files | potentially untrusted source |
| node-tar.js:48:9:50:10 | tar.x({ ... }) | node-tar.js:15:13:15:21 | req.files | node-tar.js:48:9:50:10 | tar.x({ ... }) | This Decompression depends on a $@. | node-tar.js:15:13:15:21 | req.files | potentially untrusted source |
| node-tar.js:58:19:58:30 | tarFile.name | node-tar.js:15:13:15:21 | req.files | node-tar.js:58:19:58:30 | tarFile.name | This Decompression depends on a $@. | node-tar.js:15:13:15:21 | req.files | potentially untrusted source |
| node-tar.js:59:25:59:36 | tarFile.name | node-tar.js:15:13:15:21 | req.files | node-tar.js:59:25:59:36 | tarFile.name | This Decompression depends on a $@. | node-tar.js:15:13:15:21 | req.files | potentially untrusted source |
| pako.js:21:31:21:37 | myArray | pako.js:12:14:12:22 | req.files | pako.js:21:31:21:37 | myArray | This Decompression depends on a $@. | pako.js:12:14:12:22 | req.files | potentially untrusted source |
| pako.js:32:31:32:37 | myArray | pako.js:13:14:13:22 | req.files | pako.js:32:31:32:37 | myArray | This Decompression depends on a $@. | pako.js:13:14:13:22 | req.files | potentially untrusted source |
| unbzip2.js:12:50:12:54 | bz2() | unbzip2.js:12:25:12:42 | req.query.FilePath | unbzip2.js:12:50:12:54 | bz2() | This Decompression depends on a $@. | unbzip2.js:12:25:12:42 | req.query.FilePath | potentially untrusted source |
| unzipper.js:16:23:16:63 | unzippe ... ath' }) | unzipper.js:13:40:13:48 | req.files | unzipper.js:16:23:16:63 | unzippe ... ath' }) | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:19:23:19:41 | unzipper.ParseOne() | unzipper.js:13:40:13:48 | req.files | unzipper.js:19:23:19:41 | unzipper.ParseOne() | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:24:15:24:30 | unzipper.Parse() | unzipper.js:13:40:13:48 | req.files | unzipper.js:24:15:24:30 | unzipper.Parse() | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:34:15:34:30 | unzipper.Parse() | unzipper.js:13:40:13:48 | req.files | unzipper.js:34:15:34:30 | unzipper.Parse() | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:41:35:41:71 | unzippe ... true }) | unzipper.js:13:40:13:48 | req.files | unzipper.js:41:35:41:71 | unzippe ... true }) | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:51:36:51:72 | unzippe ... true }) | unzipper.js:13:40:13:48 | req.files | unzipper.js:51:36:51:72 | unzippe ... true }) | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:60:23:60:38 | unzipper.Parse() | unzipper.js:13:40:13:48 | req.files | unzipper.js:60:23:60:38 | unzipper.Parse() | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| unzipper.js:73:23:73:38 | unzipper.Parse() | unzipper.js:13:40:13:48 | req.files | unzipper.js:73:23:73:38 | unzipper.Parse() | This Decompression depends on a $@. | unzipper.js:13:40:13:48 | req.files | potentially untrusted source |
| yauzl.js:12:18:12:39 | req.fil ... le.data | yauzl.js:12:18:12:26 | req.files | yauzl.js:12:18:12:39 | req.fil ... le.data | This Decompression depends on a $@. | yauzl.js:12:18:12:26 | req.files | potentially untrusted source |
| yauzl.js:13:22:13:43 | req.fil ... le.data | yauzl.js:13:22:13:30 | req.files | yauzl.js:13:22:13:43 | req.fil ... le.data | This Decompression depends on a $@. | yauzl.js:13:22:13:30 | req.files | potentially untrusted source |
| yauzl.js:14:34:14:55 | req.fil ... le.data | yauzl.js:14:34:14:42 | req.files | yauzl.js:14:34:14:55 | req.fil ... le.data | This Decompression depends on a $@. | yauzl.js:14:34:14:42 | req.files | potentially untrusted source |
| yauzl.js:39:9:39:27 | zipfile.readEntry() | yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:39:9:39:27 | zipfile.readEntry() | This Decompression depends on a $@. | yauzl.js:37:16:37:33 | req.query.filePath | potentially untrusted source |
| yauzl.js:41:64:41:73 | readStream | yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:41:64:41:73 | readStream | This Decompression depends on a $@. | yauzl.js:37:16:37:33 | req.query.filePath | potentially untrusted source |
| yauzl.js:43:21:43:39 | zipfile.readEntry() | yauzl.js:37:16:37:33 | req.query.filePath | yauzl.js:43:21:43:39 | zipfile.readEntry() | This Decompression depends on a $@. | yauzl.js:37:16:37:33 | req.query.filePath | potentially untrusted source |
| zlib.js:29:9:29:20 | zipFile.data | zlib.js:15:19:15:27 | req.files | zlib.js:29:9:29:20 | zipFile.data | This Decompression depends on a $@. | zlib.js:15:19:15:27 | req.files | potentially untrusted source |
| zlib.js:33:9:33:20 | zipFile.data | zlib.js:15:19:15:27 | req.files | zlib.js:33:9:33:20 | zipFile.data | This Decompression depends on a $@. | zlib.js:15:19:15:27 | req.files | potentially untrusted source |
| zlib.js:38:9:38:20 | zipFile.data | zlib.js:15:19:15:27 | req.files | zlib.js:38:9:38:20 | zipFile.data | This Decompression depends on a $@. | zlib.js:15:19:15:27 | req.files | potentially untrusted source |
| zlib.js:63:21:63:32 | zipFile.data | zlib.js:17:18:17:26 | req.files | zlib.js:63:21:63:32 | zipFile.data | This Decompression depends on a $@. | zlib.js:17:18:17:26 | req.files | potentially untrusted source |
| zlib.js:64:20:64:31 | zipFile.data | zlib.js:17:18:17:26 | req.files | zlib.js:64:20:64:31 | zipFile.data | This Decompression depends on a $@. | zlib.js:17:18:17:26 | req.files | potentially untrusted source |
| zlib.js:65:31:65:42 | zipFile.data | zlib.js:17:18:17:26 | req.files | zlib.js:65:31:65:42 | zipFile.data | This Decompression depends on a $@. | zlib.js:17:18:17:26 | req.files | potentially untrusted source |
| zlib.js:77:22:77:40 | zlib.createGunzip() | zlib.js:19:24:19:32 | req.files | zlib.js:77:22:77:40 | zlib.createGunzip() | This Decompression depends on a $@. | zlib.js:19:24:19:32 | req.files | potentially untrusted source |
| zlib.js:78:22:78:39 | zlib.createUnzip() | zlib.js:19:24:19:32 | req.files | zlib.js:78:22:78:39 | zlib.createUnzip() | This Decompression depends on a $@. | zlib.js:19:24:19:32 | req.files | potentially untrusted source |
| zlib.js:79:22:79:50 | zlib.cr ... press() | zlib.js:19:24:19:32 | req.files | zlib.js:79:22:79:50 | zlib.cr ... press() | This Decompression depends on a $@. | zlib.js:19:24:19:32 | req.files | potentially untrusted source |
| zlib.js:87:9:87:27 | zlib.createGunzip() | zlib.js:21:32:21:40 | req.files | zlib.js:87:9:87:27 | zlib.createGunzip() | This Decompression depends on a $@. | zlib.js:21:32:21:40 | req.files | potentially untrusted source |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-522-DecompressionBombs/DecompressionBombs.ql

View File

@@ -0,0 +1,37 @@
const AdmZip = require("adm-zip");
const express = require('express')
const fileUpload = require("express-fileupload");
const fs = require("fs");
const app = express();
const port = 3000;
app.use(fileUpload());
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
});
app.post('/upload', (req, res) => {
zipBomb(req.files.zipBombFile)
res.send('Hello World!')
});
function zipBomb(tarFile) {
fs.writeFileSync(tarFile.name, tarFile.data);
// or using fs.writeFile
// file path is a tmp file name that can get from DB after saving to DB with remote file upload
// so the input file name will come from a DB source
const admZip
= new AdmZip(tarFile.data);
const zipEntries = admZip.getEntries();
zipEntries.forEach(function (zipEntry) {
if (zipEntry.entryName === "my_file.txt") {
console.log(zipEntry.getData().toString("utf8"));
}
});
// outputs the content of file named 10GB
console.log(admZip.readAsText("10GB"));
// extracts the specified file to the specified location
admZip.extractEntryTo("10GB", "/tmp/", false, true);
// extracts everything
admZip.extractAllTo("./tmp", true);
}

View File

@@ -0,0 +1,16 @@
const decompress = require('decompress');
const express = require('express')
const fileUpload = require("express-fileupload");
const app = express();
app.use(fileUpload());
app.listen(3000, () => {
});
app.post('/upload', async (req, res) => {
decompress(req.query.filePath, 'dist').then(files => {
console.log('done!');
});
res.send("OK")
});

Some files were not shown because too many files have changed in this diff Show More