diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index df455c4365bb..204064e91482 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -1,11 +1,5 @@ # Improvements to C# analysis -> NOTES -> -> Please describe your changes in terms that are suitable for -> customers to read. These notes will have only minor tidying up -> before they are published as part of the release notes. - ## General improvements * Control flow analysis has been improved for `catch` clauses with filters. @@ -14,25 +8,25 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Arbitrary file write during zip extraction ("Zip Slip") (`cs/zipslip`) | security, external/cwe/cwe-022 | Identifies zip extraction routines which allow arbitrary file overwrite vulnerabilities. +| Arbitrary file write during zip extraction ("Zip Slip") (`cs/zipslip`) | security, external/cwe/cwe-022 | Identifies zip extraction routines which allow arbitrary file overwrite vulnerabilities. | | Local scope variable shadows member (`cs/local-shadows-member`) | maintainability, readability | Replaces the existing queries Local variable shadows class member (`cs/local-shadows-class-member`), Local variable shadows struct member (`cs/local-shadows-struct-member`), Parameter shadows class member (`cs/parameter-shadows-class-member`), and Parameter shadows struct member (`cs/parameter-shadows-struct-member`). | ## Changes to existing queries | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| [Constant condition](https://help.semmle.com/wiki/display/CSHARP/Constant+condition) (`cs/constant-condition`) | More results | The query has been generalized to cover both Null-coalescing left operand is constant (`cs/constant-null-coalescing`) and Switch selector is constant (`cs/constant-switch-selector`). | +| Constant condition (`cs/constant-condition`) | More results | The query has been generalized to report alerts for the old queries Null-coalescing left operand is constant (`cs/constant-null-coalescing`) and Switch selector is constant (`cs/constant-switch-selector`). | | Exposing internal representation (`cs/expose-implementation`) | Different results | The query has been rewritten, based on the [equivalent Java query](https://help.semmle.com/wiki/display/JAVA/Exposing+internal+representation). | -| Local variable shadows class member(`cs/local-shadows-class-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| Local variable shadows struct member (`cs/local-shadows-struct-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| [Missing Dispose call on local IDisposable](https://help.semmle.com/wiki/display/CSHARP/Missing+Dispose+call+on+local+IDisposable) (`cs/local-not-disposed`) | Fewer results | The query identifies more cases where the local variable may be disposed by a library call. | -| [Nested loops with same variable](https://help.semmle.com/wiki/display/CSHARP/Nested+loops+with+same+variable) (`cs/nested-loops-with-same-variable`) | Fewer results | Results are no longer highlighted in nested loops that share the same condition, and do not use the variable after the inner loop. | -| Null-coalescing left operand is constant (`cs/constant-null-coalescing`) | No results | The query has been removed, as it is now covered by Constant condition (`cs/constant-condition`). | -| Parameter shadows class member (`cs/parameter-shadows-class-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| Parameter shadows struct member (`cs/parameter-shadows-struct-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| [Potentially incorrect CompareTo(...) signature](https://help.semmle.com/wiki/display/CSHARP/Potentially+incorrect+CompareTo%28...%29+signature) (`cs/wrong-compareto-signature`) | Fewer results | Results are no longer highlighted in constructed types. | -| Switch selector is constant (`cs/constant-switch-selector`) | No results | The query has been removed, as it is now covered by Constant condition (`cs/constant-condition`). | -| [Useless upcast](https://help.semmle.com/wiki/display/CSHARP/Useless+upcast) (`cs/useless-upcast`) | Fewer results | The query has been improved to cover more cases where upcasts may be needed. | +| Local variable shadows class member (`cs/local-shadows-class-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Local variable shadows struct member (`cs/local-shadows-struct-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Missing Dispose call on local IDisposable (`cs/local-not-disposed`) | Fewer false positive results | The query identifies more cases where the local variable may be disposed by a library call. | +| Nested loops with same variable (`cs/nested-loops-with-same-variable`) | Fewer false positive results | Results are no longer highlighted in nested loops that share the same condition, and do not use the variable after the inner loop. | +| Null-coalescing left operand is constant (`cs/constant-null-coalescing`) | No results | The query has been removed, as alerts for this problem are now reported by the new query: Constant condition (`cs/constant-condition`). | +| Parameter shadows class member (`cs/parameter-shadows-class-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Parameter shadows struct member (`cs/parameter-shadows-struct-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Potentially incorrect CompareTo(...) signature (`cs/wrong-compareto-signature`) | Fewer false positive results | Results are no longer highlighted in constructed types. | +| Switch selector is constant (`cs/constant-switch-selector`) | No results | The query has been removed, as alerts for this problem are now reported by the new query: Constant condition (`cs/constant-condition`). | +| Useless upcast (`cs/useless-upcast`) | Fewer false positive results | The query has been improved to cover more cases where upcasts may be needed. | ## Changes to code extraction @@ -40,12 +34,11 @@ * The `when` part of constant cases is now extracted. * Fixed a bug where `while(x is T y) ...` was not extracted correctly. -* *Series of bullet points* - ## Changes to QL libraries -* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyse the CIL code in the library to determine this. -* Several control flow graph entities have been renamed (the old names still exist for backwards compatibility): +* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyze the CIL code in the library to determine this. +* The predicate `getCondition()` has been moved from `TypeCase` to `CaseStmt`. It is now possible to get the condition of a `ConstCase` using its `getCondition()` predicate. +* Several control flow graph entities have been renamed (the old names are deprecated but are still available in this release for backwards compatibility): - `ControlFlowNode` has been renamed to `ControlFlow::Node`. - `CallableEntryNode` has been renamed to `ControlFlow::Nodes::EntryNode`. - `CallableExitNode` has been renamed to `ControlFlow::Nodes::ExitNode`. @@ -63,4 +56,5 @@ - `ControlFlowEdgeGotoCase` has been renamed to `ControlFlow::SuccessorTypes::GotoCaseSuccessor`. - `ControlFlowEdgeGotoDefault` has been renamed to `ControlFlow::SuccessorTypes::GotoDefaultSuccessor`. - `ControlFlowEdgeException` has been renamed to `ControlFlow::SuccessorTypes::ExceptionSuccessor`. -* The predicate `getCondition()` has been moved from `TypeCase` to `CaseStmt`. It is now possible to get the condition of a `ConstCase` using its `getCondition()` predicate. + +> You should update any custom queries that use these entities to ensure that they continue working when the old names are removed in a future release. diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 9d0ad3443e88..4be9e6dbf9e7 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -2,15 +2,13 @@ ## General improvements -* Additional heuristics have been added to `semmle.javascript.heuristics`. Add `import semmle.javascript.heuristics.all` to a query in order to activate all of the heuristics at once. - -* Modelling of data flow through destructuring assignments has been improved. This may give additional results for the security queries and other queries that rely on data flow. +* Improved modeling of data flow through destructuring assignments may give additional results for the security queries and other queries that rely on data flow. -* Modelling of global variables has been improved. This may give more true-positive results and fewer false-positive results for a variety of queries. +* Improved modeling of global variables may give more true-positive results and fewer false-positive results for a variety of queries. -* Modelling of re-export declarations has been improved. This may result in fewer false-positive results for a variety of queries. +* Improved modeling of re-export declarations may result in fewer false-positive results for a variety of queries. -* Modelling of taint flow through array operations has been improved. This may give additional results for the security queries. +* Improved modeling of taint flow through array operations may give additional results for the security queries. * The taint tracking library recognizes more ways in which taint propagates. In particular, some flow through string formatters is now recognized. This may give additional results for the security queries. @@ -18,74 +16,86 @@ * Type inference for simple function calls has been improved. This may give additional results for queries that rely on type inference. +* Additional heuristics have been added to `semmle.javascript.heuristics`. Add `import semmle.javascript.heuristics.all` to a query in order to activate all of the heuristics at once. + +* Handling of ambient TypeScript code has been improved. As a result, fewer false-positive results will be reported in `.d.ts` files. + * Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following libraries: - - [bluebird](https://bluebirdjs.com) - - [browserid-crypto](https://github.com/mozilla/browserid-crypto) - - [compose-function](https://github.com/stoeffel/compose-function) - - [cookie-parser](https://github.com/expressjs/cookie-parser) - - [cookie-session](https://github.com/expressjs/cookie-session) - - [crypto-js](https://github.com/https://github.com/brix/crypto-js) - - [deep-assign](https://github.com/sindresorhus/deep-assign) - - [deep-extend](https://github.com/unclechu/node-deep-extend) - - [deep-merge](https://github.com/Raynos/deep-merge) - - [deep](https://github.com/jeffomatic/deep) - - [deepmerge](https://github.com/KyleAMathews/deepmerge) - - [defaults-deep](https://github.com/jonschlinkert/defaults-deep) - - [defaults](https://github.com/tmpvar/defaults) - - [dottie](https://github.com/mickhansen/dottie.js) - - [dotty](https://github.com/deoxxa/dotty) - - [ent](https://github.com/substack/node-ent) - - [entities](https://github.com/fb55/node-entities) - - [escape-goat](https://github.com/sindresorhus/escape-goat) - - [express-jwt](https://github.com/auth0/express-jwt) - - [express-session](https://github.com/expressjs/session) - - [extend-shallow](https://github.com/jonschlinkert/extend-shallow) - - [extend](https://github.com/justmoon/node-extend) - - [extend2](https://github.com/eggjs/extend2) - - [fast-json-parse](https://github.com/mcollina/fast-json-parse) - - [forge](https://github.com/digitalbazaar/forge) - - [format-util](https://github.com/tmpfs/format-util) - - [global](https://github.com/Raynos/global) - - [he](https://github.com/mathiasbynens/he) - - [html-entities](https://github.com/mdevils/node-html-entities) - - [jquery](https://jquery.com) - - [js-extend](https://github.com/vmattos/js-extend) - - [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors) - - [json-parse-safe](https://github.com/joaquimserafim/json-parse-safe) - - [json-safe-parse](https://github.com/bahamas10/node-json-safe-parse) - - [just-compose](https://github.com/angus-c/just) - - [just-extend](https://github.com/angus-c/just) - - [lodash](https://lodash.com) - - [merge-deep](https://github.com/jonschlinkert/merge-deep) - - [merge-options](https://github.com/schnittstabil/merge-options) - - [merge](https://github.com/yeikos/js.merge) - - [mixin-deep](https://github.com/jonschlinkert/mixin-deep) - - [mixin-object](https://github.com/jonschlinkert/mixin-object) - - [MySQL2](https://github.com/sidorares/node-mysql2) - - [node.extend](https://github.com/dreamerslab/node.extend) - - [object-assign](https://github.com/sindresorhus/object-assign) - - [object.assign](https://github.com/ljharb/object.assign) - - [object.defaults](https://github.com/jonschlinkert/object.defaults) - - [parse-json](https://github.com/sindresorhus/parse-json) - - [printf](https://github.com/adaltas/node-printf) - - [printj](https://github.com/SheetJS/printj) - - [q](https://documentup.com/kriskowal/q/) - - [ramda](https://ramdajs.com) - - [React Native](https://facebook.github.io/react-native/) - - [safe-json-parse](https://github.com/Raynos/safe-json-parse) - - [sanitize](https://github.com/pocketly/node-sanitize) - - [sanitizer](https://github.com/theSmaw/Caja-HTML-Sanitizer) - - [smart-extend](https://github.com/danielkalen/smart-extend) - - [sprintf.js](https://github.com/alexei/sprintf.js) - - [string-template](https://github.com/Matt-Esch/string-template) - - [underscore](https://underscorejs.org) - - [util-extend](https://github.com/isaacs/util-extend) - - [utils-merge](https://github.com/jaredhanson/utils-merge) - - [validator](https://github.com/chriso/validator.js) - - [xss](https://github.com/leizongmin/js-xss) - - [xtend](https://github.com/Raynos/xtend) - -* Handling of ambient TypeScript code has been improved. As a result, fewer false positives will be reported in `.d.ts` files. + [axios](https://github.com/axios/axios), + [bluebird](https://bluebirdjs.com), + [browserid-crypto](https://github.com/mozilla/browserid-crypto), + [compose-function](https://github.com/stoeffel/compose-function), + [cookie-parser](https://github.com/expressjs/cookie-parser), + [cookie-session](https://github.com/expressjs/cookie-session), + [cross-fetch](https://github.com/lquixada/cross-fetch), + [crypto-js](https://github.com/https://github.com/brix/crypto-js), + [deep-assign](https://github.com/sindresorhus/deep-assign), + [deep-extend](https://github.com/unclechu/node-deep-extend), + [deep-merge](https://github.com/Raynos/deep-merge), + [deep](https://github.com/jeffomatic/deep), + [deepmerge](https://github.com/KyleAMathews/deepmerge), + [defaults-deep](https://github.com/jonschlinkert/defaults-deep), + [defaults](https://github.com/tmpvar/defaults), + [dottie](https://github.com/mickhansen/dottie.js), + [dotty](https://github.com/deoxxa/dotty), + [ent](https://github.com/substack/node-ent), + [entities](https://github.com/fb55/node-entities), + [escape-goat](https://github.com/sindresorhus/escape-goat), + [express-jwt](https://github.com/auth0/express-jwt), + [express-session](https://github.com/expressjs/session), + [extend-shallow](https://github.com/jonschlinkert/extend-shallow), + [extend](https://github.com/justmoon/node-extend), + [extend2](https://github.com/eggjs/extend2), + [fast-json-parse](https://github.com/mcollina/fast-json-parse), + [forge](https://github.com/digitalbazaar/forge), + [format-util](https://github.com/tmpfs/format-util), + [got](https://github.com/sindresorhus/got), + [global](https://github.com/Raynos/global), + [he](https://github.com/mathiasbynens/he), + [html-entities](https://github.com/mdevils/node-html-entities), + [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch), + [jquery](https://jquery.com), + [js-extend](https://github.com/vmattos/js-extend), + [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors), + [json-parse-safe](https://github.com/joaquimserafim/json-parse-safe), + [json-safe-parse](https://github.com/bahamas10/node-json-safe-parse), + [just-compose](https://github.com/angus-c/just), + [just-extend](https://github.com/angus-c/just), + [lodash](https://lodash.com), + [merge-deep](https://github.com/jonschlinkert/merge-deep), + [merge-options](https://github.com/schnittstabil/merge-options), + [merge](https://github.com/yeikos/js.merge), + [mixin-deep](https://github.com/jonschlinkert/mixin-deep), + [mixin-object](https://github.com/jonschlinkert/mixin-object), + [MySQL2](https://github.com/sidorares/node-mysql2), + [node.extend](https://github.com/dreamerslab/node.extend), + [node-fetch](https://github.com/bitinn/node-fetch), + [object-assign](https://github.com/sindresorhus/object-assign), + [object.assign](https://github.com/ljharb/object.assign), + [object.defaults](https://github.com/jonschlinkert/object.defaults), + [parse-json](https://github.com/sindresorhus/parse-json), + [printf](https://github.com/adaltas/node-printf), + [printj](https://github.com/SheetJS/printj), + [q](https://documentup.com/kriskowal/q/), + [ramda](https://ramdajs.com), + [request](https://github.com/request/request), + [request-promise](https://github.com/request/request-promise), + [request-promise-any](https://github.com/request/request-promise-any), + [request-promise-native](https://github.com/request/request-promise-native), + [React Native](https://facebook.github.io/react-native/), + [safe-json-parse](https://github.com/Raynos/safe-json-parse), + [sanitize](https://github.com/pocketly/node-sanitize), + [sanitizer](https://github.com/theSmaw/Caja-HTML-Sanitizer), + [smart-extend](https://github.com/danielkalen/smart-extend), + [sprintf.js](https://github.com/alexei/sprintf.js), + [string-template](https://github.com/Matt-Esch/string-template), + [superagent](https://github.com/visionmedia/superagent), + [underscore](https://underscorejs.org), + [util-extend](https://github.com/isaacs/util-extend), + [utils-merge](https://github.com/jaredhanson/utils-merge), + [validator](https://github.com/chriso/validator.js), + [xss](https://github.com/leizongmin/js-xss), + [xtend](https://github.com/Raynos/xtend). ## New queries @@ -94,39 +104,36 @@ | Clear-text logging of sensitive information (`js/clear-text-logging`) | security, external/cwe/cwe-312, external/cwe/cwe-315, external/cwe/cwe-359 | Highlights logging of sensitive information, indicating a violation of [CWE-312](https://cwe.mitre.org/data/definitions/312.html). Results shown on LGTM by default. | | Disabling Electron webSecurity (`js/disabling-electron-websecurity`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `webSecurity` property set to false. Results shown on LGTM by default. | | Enabling Electron allowRunningInsecureContent (`js/enabling-electron-insecure-content`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `allowRunningInsecureContent` property set to true. Results shown on LGTM by default. | +| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results are hidden on LGTM by default. | | Use of externally-controlled format string (`js/tainted-format-string`) | security, external/cwe/cwe-134 | Highlights format strings containing user-provided data, indicating a violation of [CWE-134](https://cwe.mitre.org/data/definitions/134.html). Results shown on LGTM by default. | ## Changes to existing queries | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| Arguments redefined | Fewer results | This rule previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. | -| Comparison between inconvertible types | Fewer results | This rule now flags fewer comparisons involving parameters. | -| Comparison between inconvertible types | Lower severity | The severity of this rule has been revised to "warning". | -| CORS misconfiguration for credentials transfer | More true-positive results | This rule now treats header names case-insensitively. | -| Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. | -| Incomplete string escaping or encoding | Better name, more true-positive results | This rule has been renamed to more clearly reflect its purpose. Also, it now recognizes incomplete URL encoding and decoding. | -| Insecure randomness | More true-positive results | This rule now recognizes secret cryptographic keys. | -| Misleading indentation after control statement | Fewer results | This rule temporarily ignores TypeScript files. | -| Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | -| Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. | -| Omitted array element | Fewer results | This rule temporarily ignores TypeScript files. | -| Reflected cross-site scripting | Fewer false-positive results | This rule now treats header names case-insensitively. | -| Semicolon insertion | Fewer results | This rule temporarily ignores TypeScript files. | -| Server-side URL redirect | More true-positive results | This rule now treats header names case-insensitively. | -| Superfluous trailing arguments | Fewer false-positive results | This rule now ignores calls to some empty functions. | -| Type confusion through parameter tampering | Fewer false-positive results | This rule no longer flags emptiness checks. | -| Uncontrolled command line | More true-positive results | This rule now recognizes indirect command injection through `sh -c` and similar. | -| Unused variable | Fewer results | This rule no longer flags class expressions that could be made anonymous. While technically true, these results are not interesting. | -| Unused variable | Renamed | This rule has been renamed to "Unused variable, import, function or class" to reflect the fact that it flags different kinds of unused program elements. | -| Use of incompletely initialized object| Fewer results | This rule now flags the constructor instead its errorneous `this` or `super` expressions. | -| Useless conditional | Fewer results | This rule no longer flags uses of boolean return values. | -| Useless conditional | Fewer results | This rule now flags fewer comparisons involving parameters. | +| Arguments redefined (`js/arguments-redefinition`) | Fewer results | This query previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. | +| Comparison between inconvertible types (`js/comparison-between-incompatible-types`) | Fewer results | This query now flags fewer comparisons involving parameters. The severity of this query has been revised to "warning". | +| CORS misconfiguration for credentials transfer (`js/cors-misconfiguration-for-credentials`) | More true-positive results | This query now treats header names case-insensitively. | +| Hard-coded credentials (`js/hardcoded-credentials`) | More true-positive results | This query now recognizes secret cryptographic keys. | +| Incomplete string escaping or encoding (`js/incomplete-sanitization`) | New name, more true-positive results | The "Incomplete sanitization" query has been renamed to more clearly reflect its purpose. It now recognizes incomplete URL encoding and decoding. | +| Insecure randomness (`js/insecure-randomness`) | More true-positive results | This query now recognizes secret cryptographic keys. | +| Misleading indentation after control statement (`js/misleading-indentation-after-control-statement`) | Fewer results | This query temporarily ignores TypeScript files. | +| Missing rate limiting (`js/missing-rate-limiting`) | More true-positive results, fewer false-positive results | This query now recognizes additional rate limiters and expensive route handlers. | +| Omitted array element (`js/omitted-array-element`)| Fewer results | This query temporarily ignores TypeScript files. | +| Reflected cross-site scripting (`js/reflected-xss`) | Fewer false-positive results | This query now treats header names case-insensitively. | +| Semicolon insertion (`js/automatic-semicolon-insertion`) | Fewer results | This query temporarily ignores TypeScript files. | +| Server-side URL redirect (`js/server-side-unvalidated-url-redirection`) | More true-positive results | This query now treats header names case-insensitively. | +| Superfluous trailing arguments (`js/superfluous-trailing-arguments`) | Fewer false-positive results | This query now ignores calls to some empty functions. | +| Type confusion through parameter tampering (`js/type-confusion-through-parameter-tampering`) | Fewer false-positive results | This query no longer flags emptiness checks. | +| Uncontrolled command line (`js/command-line-injection`) | More true-positive results | This query now recognizes indirect command injection through `sh -c` and similar. | +| Unused variable, import, function or class (`js/unused-local-variable`) | New name, fewer results | The "Unused variable" query has been renamed to reflect the fact that it highlights different kinds of unused program elements. In addition, the query no longer highlights class expressions that could be made anonymous. While technically true, these results are not interesting. | +| Use of incompletely initialized object (`js/incomplete-object-initialization`) | Fewer results | This query now highlights the constructor instead of its erroneous `this` or `super` expressions. | +| Useless conditional (`js/trivial-conditional`) | Fewer results | This query no longer flags uses of boolean return values and highlights fewer comparisons involving parameters. | ## Changes to QL libraries -* HTTP and HTTPS requests made using the Node.js `http.request` and `https.request` APIs and the Electron `Electron.net.request` and `Electron.ClientRequest` APIs are modeled as `RemoteFlowSources`. -* HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly` and `RouteHandler.getAResponseHeader` is now always a lower-case string. +* HTTP and HTTPS requests made using the Node.js `http.request` and `https.request` APIs, and the Electron `Electron.net.request` and `Electron.ClientRequest` APIs, are modeled as `RemoteFlowSources`. +* HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly`, and `RouteHandler.getAResponseHeader` are now always a lower-case string. * New AST nodes have been added for TypeScript 2.9 and 3.0 features. -* The class `JsonParseCall` has been deprecated. Use `JsonParserCall` instead. +* The class `JsonParseCall` has been deprecated. Update your queries to use `JsonParserCall` instead. * The handling of spread arguments in the data flow library has been changed: `DataFlow::InvokeNode.getArgument(i)` is now only defined when there is no spread argument at or before argument position `i`, and similarly `InvokeNode.getNumArgument` is only defined for invocations without spread arguments. diff --git a/cpp/ql/src/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql b/cpp/ql/src/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql index b3ab5828094d..51302b710f2c 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql @@ -29,11 +29,20 @@ predicate isEffectivelyConstAccess(VariableAccess a) ) } -from FunctionCall fc, VariableAccess src -where fc.getTarget().hasName("strcat") and - src = fc.getArgument(1) and - not src.getType() instanceof ArrayType and +class StrcatSource extends VariableAccess { + FunctionCall strcat; + + StrcatSource() { + strcat.getTarget().hasName("strcat") and + this = strcat.getArgument(1) + } + + FunctionCall getStrcatCall() { result = strcat } +} + +from StrcatSource src +where not src.getType() instanceof ArrayType and not exists(BufferSizeExpr bse | bse.getArg().(VariableAccess).getTarget() = src.getTarget()) and not isEffectivelyConstAccess(src) -select fc, "Always check the size of the source buffer when using strcat." +select src.getStrcatCall(), "Always check the size of the source buffer when using strcat." diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index 48878be87ce1..26debb63a24e 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -29,3 +29,4 @@ + semmlecode-javascript-queries/Security/CWE-807/DifferentKindsComparisonBypass.ql: /Security/CWE/CWE-807 + semmlecode-javascript-queries/Security/CWE-843/TypeConfusionThroughParameterTampering.ql: /Security/CWE/CWE-834 + semmlecode-javascript-queries/Security/CWE-916/InsufficientPasswordHash.ql: /Security/CWE/CWE-916 ++ semmlecode-javascript-queries/Security/CWE-918/RequestForgery.ql: /Security/CWE/CWE-918 diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp new file mode 100644 index 000000000000..83b541d3a24b --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp @@ -0,0 +1,79 @@ + + + + +

+ + Directly incorporating user input into an HTTP request + without validating the input can facilitate different kinds of request + forgery attacks, where the attacker essentially controls the request. + + If the vulnerable request is in server-side code, then security + mechanisms, such as external firewalls, can be bypassed. + + If the vulnerable request is in client-side code, then unsuspecting + users can send malicious requests to other servers, potentially + resulting in a DDOS attack. + +

+
+ + + +

+ + To guard against request forgery, it is advisable to avoid + putting user input directly into a remote request. If a flexible + remote request mechanism is required, it is recommended to maintain a + list of authorized request targets and choose from that list based on + the user input provided. + +

+ +
+ + + +

+ + The following example shows an HTTP request parameter + being used directly in a URL request without validating the input, + which facilitates an SSRF attack. The request + http.get(...) is vulnerable since attackers can choose + the value of target to be anything they want. For + instance, the attacker can choose + "internal.example.com/#" as the target, causing the URL + used in the request to be + "https://internal.example.com/#.example.com/data". + +

+ +

+ + A request to https://internal.example.com may + be problematic if that server is not meant to be + directly accessible from the attacker's machine. + +

+ + + +

+ + One way to remedy the problem is to use the user input to + select a known fixed string before performing the request: + +

+ + + +
+ + + +
  • OWASP: SSRF
  • + +
    +
    diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.ql b/javascript/ql/src/Security/CWE-918/RequestForgery.ql new file mode 100644 index 000000000000..8035f2ef56fb --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.ql @@ -0,0 +1,18 @@ +/** + * @name Uncontrolled data used in remote request + * @description Sending remote requests with user-controlled data allows for request forgery attacks. + * @kind problem + * @problem.severity error + * @precision medium + * @id js/request-forgery + * @tags security + * external/cwe/cwe-918 + */ + +import javascript +import semmle.javascript.security.dataflow.RequestForgery::RequestForgery + +from Configuration cfg, DataFlow::Node source, Sink sink, DataFlow::Node request +where cfg.hasFlow(source, sink) and + request = sink.getARequest() +select request, "The $@ of this request depends on $@.", sink, sink.getKind(), source, "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js new file mode 100644 index 000000000000..d0eb8b56b0fc --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js @@ -0,0 +1,12 @@ +import http from 'http'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var target = url.parse(request.url, true).query.target; + + // BAD: `target` is controlled by the attacker + http.get('https://' + target + ".example.com/data/", res => { + // process request response ... + }); + +}); diff --git a/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js new file mode 100644 index 000000000000..2c1662a5a1e7 --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js @@ -0,0 +1,19 @@ +import http from 'http'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var target = url.parse(request.url, true).query.target; + + var subdomain; + if (target === 'EU') { + subdomain = "europe" + } else { + subdomain = "world" + } + + // GOOD: `subdomain` is controlled by the server + http.get('https://' + subdomain + ".example.com/data/", res => { + // process request response ... + }); + +}); diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 436520d3fcf1..16bdff91f76a 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -54,6 +54,7 @@ import semmle.javascript.frameworks.AWS import semmle.javascript.frameworks.Azure import semmle.javascript.frameworks.Babel import semmle.javascript.frameworks.ComposedFunctions +import semmle.javascript.frameworks.ClientRequests import semmle.javascript.frameworks.Credentials import semmle.javascript.frameworks.CryptoLibraries import semmle.javascript.frameworks.DigitalOcean diff --git a/javascript/ql/src/semmle/javascript/Stmt.qll b/javascript/ql/src/semmle/javascript/Stmt.qll index facc438e12b4..486298a38965 100644 --- a/javascript/ql/src/semmle/javascript/Stmt.qll +++ b/javascript/ql/src/semmle/javascript/Stmt.qll @@ -607,6 +607,10 @@ abstract class EnhancedForLoop extends LoopStmt { override Stmt getBody() { result = getChildStmt(2) } + + override ControlFlowNode getFirstControlFlowNode() { + result = getIteratorExpr().getFirstControlFlowNode() + } } /** A `for`-`in` loop. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll new file mode 100644 index 000000000000..a19b5effca35 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -0,0 +1,194 @@ +/** + * Provides classes for modelling the client-side of a URL request. + * + * Subclass `ClientRequest` to refine the behavior of the analysis on existing client requests. + * Subclass `CustomClientRequest` to introduce new kinds of client requests. + */ + +import javascript + +/** + * A call that performs a request to a URL. + */ +abstract class CustomClientRequest extends DataFlow::InvokeNode { + + /** + * Gets the URL of the request. + */ + abstract DataFlow::Node getUrl(); +} + +/** + * A call that performs a request to a URL. + */ +class ClientRequest extends DataFlow::InvokeNode { + + CustomClientRequest custom; + + ClientRequest() { + this = custom + } + + /** + * Gets the URL of the request. + */ + DataFlow::Node getUrl() { + result = custom.getUrl() + } +} + +/** + * Gets name of an HTTP request method, in all-lowercase. + */ +private string httpMethodName() { + result = any(HTTP::RequestMethodName m).toLowerCase() +} + +/** + * Gets the name of a property that likely contains a URL value. + */ +private string urlPropertyName() { + result = "uri" or + result = "url" +} + +/** + * A model of a URL request made using the `request` library. + */ +private class RequestUrlRequest extends CustomClientRequest { + + DataFlow::Node url; + + RequestUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + ( + moduleName = "request" or + moduleName = "request-promise" or + moduleName = "request-promise-any" or + moduleName = "request-promise-native" + ) and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName()) + ) and + ( + url = getArgument(0) or + url = getOptionArgument(0, urlPropertyName()) + ) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} + +/** + * A model of a URL request made using the `axios` library. + */ +private class AxiosUrlRequest extends CustomClientRequest { + + DataFlow::Node url; + + AxiosUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "axios" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName()) or + callee = DataFlow::moduleMember(moduleName, "request") + ) and + ( + url = getArgument(0) or + // depends on the method name and the call arity, over-approximating slightly in the name of simplicity + url = getOptionArgument([0..2], urlPropertyName()) + ) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} + +/** + * A model of a URL request made using an implementation of the `fetch` API. + */ +private class FetchUrlRequest extends CustomClientRequest { + + DataFlow::Node url; + + FetchUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + ( + moduleName = "node-fetch" or + moduleName = "cross-fetch" or + moduleName = "isomorphic-fetch" + ) and + callee = DataFlow::moduleImport(moduleName) and + url = getArgument(0) + ) + or + ( + this = DataFlow::globalVarRef("fetch").getACall() and + url = getArgument(0) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} + +/** + * A model of a URL request made using the `got` library. + */ +private class GotUrlRequest extends CustomClientRequest { + + DataFlow::Node url; + + GotUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "got" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, "stream") + ) and + url = getArgument(0) and not exists (getOptionArgument(1, "baseUrl")) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} + +/** + * A model of a URL request made using the `superagent` library. + */ +private class SuperAgentUrlRequest extends CustomClientRequest { + + DataFlow::Node url; + + SuperAgentUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "superagent" and + callee = DataFlow::moduleMember(moduleName, httpMethodName()) and + url = getArgument(0) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index a60309c9be10..b7c80eca8031 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -33,37 +33,51 @@ module Electron { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() } } - + + /** + * A Node.js-style HTTP or HTTPS request made using an Electron module. + */ + abstract class CustomElectronClientRequest extends NodeJSLib::CustomNodeJSClientRequest {} + /** * A Node.js-style HTTP or HTTPS request made using an Electron module. */ - abstract class ClientRequest extends NodeJSLib::ClientRequest {} + class ElectronClientRequest extends NodeJSLib::NodeJSClientRequest { + + ElectronClientRequest() { + this instanceof CustomElectronClientRequest + } + + } /** * A Node.js-style HTTP or HTTPS request made using `electron.net`, for example `net.request(url)`. */ - private class NetRequest extends ClientRequest { + private class NetRequest extends CustomElectronClientRequest { NetRequest() { this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) + + override DataFlow::Node getUrl() { + result = getArgument(0) or + result = getOptionArgument(0, "url") } + } - - + /** * A Node.js-style HTTP or HTTPS request made using `electron.client`, for example `new client(url)`. */ - private class NewClientRequest extends ClientRequest { + private class NewClientRequest extends CustomElectronClientRequest { NewClientRequest() { this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation() } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::NewNode).getArgument(0) + + override DataFlow::Node getUrl() { + result = getArgument(0) or + result = getOptionArgument(0, "url") } + } @@ -75,12 +89,12 @@ module Electron { exists(NodeJSLib::ClientRequestHandler handler | this = handler.getParameter(0) and handler.getAHandledEvent() = "redirect" and - handler.getClientRequest() instanceof ClientRequest + handler.getClientRequest() instanceof ElectronClientRequest ) } override string getSourceType() { - result = "Electron ClientRequest redirect event" + result = "ElectronClientRequest redirect event" } } } \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 6a11bef5edc5..ba4dc9731e7f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -502,53 +502,47 @@ module NodeJSLib { } /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + * A data flow node that is an HTTP or HTTPS client request made by a Node.js application, for example `http.request(url)`. */ - abstract class ClientRequest extends DataFlow::DefaultSourceNode { - /** - * Gets the options object or string URL used to make the request. - */ - abstract DataFlow::Node getOptions(); + abstract class CustomNodeJSClientRequest extends CustomClientRequest { + } - + /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + * A data flow node that is an HTTP or HTTPS client request made by a Node.js application, for example `http.request(url)`. */ - private class HttpRequest extends ClientRequest { - HttpRequest() { - exists(string protocol | - ( - protocol = "http" or - protocol = "https" - ) - and - this = DataFlow::moduleImport(protocol).getAMemberCall("request") - ) - } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) + class NodeJSClientRequest extends ClientRequest { + + NodeJSClientRequest() { + this instanceof CustomNodeJSClientRequest } + } /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js process, for example `https.get(url)`. + * A model of a URL request in the Node.js `http` library. */ - private class HttpGet extends ClientRequest { - HttpGet() { - exists(string protocol | + private class NodeHttpUrlRequest extends CustomNodeJSClientRequest { + + DataFlow::Node url; + + NodeHttpUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + (moduleName = "http" or moduleName = "https") and ( - protocol = "http" or - protocol = "https" - ) - and - this = DataFlow::moduleImport(protocol).getAMemberCall("get") + callee = DataFlow::moduleMember(moduleName, any(HTTP::RequestMethodName m).toLowerCase()) + or + callee = DataFlow::moduleMember(moduleName, "request") + ) and + url = getArgument(0) ) } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) + + override DataFlow::Node getUrl() { + result = url } + } /** @@ -556,13 +550,13 @@ module NodeJSLib { */ private class ClientRequestCallbackParam extends DataFlow::ParameterNode, RemoteFlowSource { ClientRequestCallbackParam() { - exists(ClientRequest req | + exists(NodeJSClientRequest req | this = req.(DataFlow::MethodCallNode).getCallback(1).getParameter(0) ) } override string getSourceType() { - result = "ClientRequest callback parameter" + result = "NodeJSClientRequest callback parameter" } } @@ -589,7 +583,7 @@ module NodeJSLib { */ class ClientRequestHandler extends DataFlow::FunctionNode { string handledEvent; - ClientRequest clientRequest; + NodeJSClientRequest clientRequest; ClientRequestHandler() { exists(DataFlow::MethodCallNode mcn | @@ -609,7 +603,7 @@ module NodeJSLib { /** * Gets a request this callback is registered for. */ - ClientRequest getClientRequest() { + NodeJSClientRequest getClientRequest() { result = clientRequest } } @@ -626,7 +620,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest response event" + result = "NodeJSClientRequest response event" } } @@ -643,7 +637,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest data event" + result = "NodeJSClientRequest data event" } } @@ -667,7 +661,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest login event" + result = "NodeJSClientRequest login event" } } @@ -725,7 +719,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest error event" + result = "NodeJSClientRequest error event" } } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll new file mode 100644 index 000000000000..6881382672ab --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll @@ -0,0 +1,87 @@ +/** + * Provides a taint-tracking configuration for reasoning about request forgery. + */ + +import semmle.javascript.security.dataflow.RemoteFlowSources +import UrlConcatenation + +module RequestForgery { + + /** + * A data flow source for request forgery. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for request forgery. + */ + abstract class Sink extends DataFlow::Node { + /** + * Gets a request that uses this sink. + */ + abstract DataFlow::Node getARequest(); + + /** + * Gets the kind of this sink. + */ + abstract string getKind(); + + } + + /** + * A sanitizer for request forgery. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A taint tracking configuration for request forgery. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { + this = "RequestForgery" + } + + override predicate isSource(DataFlow::Node source) { + source instanceof Source + } + + override predicate isSink(DataFlow::Node sink) { + sink instanceof Sink + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isSanitizer(DataFlow::Node source, DataFlow::Node sink) { + sanitizingPrefixEdge(source, sink) + } + + } + + /** A source of remote user input, considered as a flow source for request forgery. */ + private class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * The URL of a URL request, viewed as a sink for request forgery. + */ + private class ClientRequestUrlAsSink extends Sink { + + ClientRequest request; + + ClientRequestUrlAsSink() { + this = request.getUrl() + } + + override DataFlow::Node getARequest() { + result = request + } + + override string getKind() { + result = "URL" + } + } +} diff --git a/javascript/ql/test/library-tests/CFG/CFG.expected b/javascript/ql/test/library-tests/CFG/CFG.expected index d2a79ea52a5a..c78a966ed341 100644 --- a/javascript/ql/test/library-tests/CFG/CFG.expected +++ b/javascript/ql/test/library-tests/CFG/CFG.expected @@ -644,15 +644,14 @@ | forof | 1 | entry node of functio ... ;\\n} | 2 | {\\n f ... ;\\n} | | forof | 1 | f | 1 | functio ... ;\\n} | | forof | 1 | functio ... ;\\n} | 10 | exit node of | -| forof | 2 | {\\n f ... ;\\n} | 3 | for (\\n ... )\\n ; | -| forof | 3 | for (\\n ... )\\n ; | 7 | [] | +| forof | 2 | {\\n f ... ;\\n} | 7 | [] | +| forof | 3 | for (\\n ... )\\n ; | 4 | var\\n x | +| forof | 3 | for (\\n ... )\\n ; | 9 | exit node of functio ... ;\\n} | | forof | 4 | var\\n x | 5 | x | | forof | 5 | x | 5 | x | | forof | 5 | x | 8 | ; | -| forof | 7 | [] | 4 | var\\n x | -| forof | 7 | [] | 9 | exit node of functio ... ;\\n} | -| forof | 8 | ; | 4 | var\\n x | -| forof | 8 | ; | 9 | exit node of functio ... ;\\n} | +| forof | 7 | [] | 3 | for (\\n ... )\\n ; | +| forof | 8 | ; | 3 | for (\\n ... )\\n ; | | globals | 1 | entry node of | 14 | g | | globals | 1 | var\\n x,\\n y; | 2 | x | | globals | 2 | x | 2 | x | @@ -662,15 +661,14 @@ | globals | 4 | {\\n var\\n z;\\n} | 5 | var\\n z; | | globals | 5 | var\\n z; | 6 | z | | globals | 6 | z | 6 | z | -| globals | 6 | z | 8 | for (\\n ... [])\\n ; | -| globals | 8 | for (\\n ... [])\\n ; | 11 | [] | +| globals | 6 | z | 11 | [] | +| globals | 8 | for (\\n ... [])\\n ; | 9 | var\\n a | +| globals | 8 | for (\\n ... [])\\n ; | 13 | function\\n g()\\n{\\n} | | globals | 9 | var\\n a | 10 | a | | globals | 10 | a | 10 | a | | globals | 10 | a | 12 | ; | -| globals | 11 | [] | 9 | var\\n a | -| globals | 11 | [] | 13 | function\\n g()\\n{\\n} | -| globals | 12 | ; | 9 | var\\n a | -| globals | 12 | ; | 13 | function\\n g()\\n{\\n} | +| globals | 11 | [] | 8 | for (\\n ... [])\\n ; | +| globals | 12 | ; | 8 | for (\\n ... [])\\n ; | | globals | 13 | entry node of function\\n g()\\n{\\n} | 15 | {\\n} | | globals | 13 | function\\n g()\\n{\\n} | 17 | !\\nfunction\\n h()\\n{\\n}; | | globals | 14 | g | 1 | var\\n x,\\n y; | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected new file mode 100644 index 000000000000..ad9bc6a4d196 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected @@ -0,0 +1,18 @@ +| tst.js:11:5:11:16 | request(url) | +| tst.js:13:5:13:20 | request.get(url) | +| tst.js:15:5:15:23 | request.delete(url) | +| tst.js:17:5:17:25 | request ... url }) | +| tst.js:19:5:19:23 | requestPromise(url) | +| tst.js:21:5:21:23 | superagent.get(url) | +| tst.js:23:5:23:17 | http.get(url) | +| tst.js:25:5:25:14 | axios(url) | +| tst.js:27:5:27:18 | axios.get(url) | +| tst.js:29:5:29:23 | axios({ url: url }) | +| tst.js:31:5:31:12 | got(url) | +| tst.js:33:5:33:19 | got.stream(url) | +| tst.js:35:5:35:21 | window.fetch(url) | +| tst.js:37:5:37:18 | nodeFetch(url) | +| tst.js:39:5:39:20 | net.request(url) | +| tst.js:41:5:41:29 | net.req ... url }) | +| tst.js:43:5:43:26 | new Cli ... st(url) | +| tst.js:45:5:45:35 | new Cli ... url }) | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql new file mode 100644 index 000000000000..0baf011e1df6 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql @@ -0,0 +1,4 @@ +import javascript + +from ClientRequest r +select r \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected new file mode 100644 index 000000000000..9c12f44e448f --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected @@ -0,0 +1,22 @@ +| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | +| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | +| tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:13:17:24 | { url: url } | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:20:17:22 | url | +| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:20:19:22 | url | +| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:20:21:22 | url | +| tst.js:23:5:23:17 | http.get(url) | tst.js:23:14:23:16 | url | +| tst.js:25:5:25:14 | axios(url) | tst.js:25:11:25:13 | url | +| tst.js:27:5:27:18 | axios.get(url) | tst.js:27:15:27:17 | url | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:11:29:22 | { url: url } | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:18:29:20 | url | +| tst.js:31:5:31:12 | got(url) | tst.js:31:9:31:11 | url | +| tst.js:33:5:33:19 | got.stream(url) | tst.js:33:16:33:18 | url | +| tst.js:35:5:35:21 | window.fetch(url) | tst.js:35:18:35:20 | url | +| tst.js:37:5:37:18 | nodeFetch(url) | tst.js:37:15:37:17 | url | +| tst.js:39:5:39:20 | net.request(url) | tst.js:39:17:39:19 | url | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:17:41:28 | { url: url } | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:24:41:26 | url | +| tst.js:43:5:43:26 | new Cli ... st(url) | tst.js:43:23:43:25 | url | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:23:45:34 | { url: url } | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:30:45:32 | url | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql new file mode 100644 index 000000000000..00dae0b3b732 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql @@ -0,0 +1,4 @@ +import javascript + +from ClientRequest r +select r, r.getUrl() \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js new file mode 100644 index 000000000000..025de83861f1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -0,0 +1,50 @@ +import request from 'request'; +import requestPromise from 'request-promise'; +import superagent from 'superagent'; +import http from 'http'; +import express from 'express'; +import axios from 'axios'; +import got from 'got'; +import nodeFetch from 'node-fetch'; +import {ClientRequest, net} from 'electron'; +(function() { + request(url); + + request.get(url); + + request.delete(url); + + request({ url: url }); + + requestPromise(url); + + superagent.get(url); + + http.get(url); + + axios(url); + + axios.get(url); + + axios({ url: url }); + + got(url); + + got.stream(url); + + window.fetch(url); + + nodeFetch(url); + + net.request(url); + + net.request({ url: url }); + + new ClientRequest(url); + + new ClientRequest({ url: url }); + + unknown(url); + + unknown({ url:url }); +}); diff --git a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql index 157b158d45dc..ef6eac9f2d4e 100644 --- a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql @@ -1,4 +1,4 @@ import javascript -from NodeJSLib::ClientRequest cr +from Electron::ElectronClientRequest cr select cr \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql index 157b158d45dc..0813343a6928 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql @@ -1,4 +1,4 @@ import javascript -from NodeJSLib::ClientRequest cr +from NodeJSLib::NodeJSClientRequest cr select cr \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js b/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js new file mode 100644 index 000000000000..da26a3557ade --- /dev/null +++ b/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js @@ -0,0 +1,13 @@ +function f() { + let y = false; + for (const x of [1, 2, 3]) { + if (x > 0) { + y = true; // OK + continue; + } + return; + } + if (!y) { + console.log("Hello"); + } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected new file mode 100644 index 000000000000..d114b6a8e389 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -0,0 +1,6 @@ +| tst.js:16:5:16:20 | request(tainted) | The $@ of this request depends on $@. | tst.js:16:13:16:19 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:18:5:18:24 | request.get(tainted) | The $@ of this request depends on $@. | tst.js:18:17:18:23 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:22:5:22:20 | request(options) | The $@ of this request depends on $@. | tst.js:21:19:21:25 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:24:5:24:32 | request ... ainted) | The $@ of this request depends on $@. | tst.js:24:13:24:31 | "http://" + tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:26:5:26:43 | request ... ainted) | The $@ of this request depends on $@. | tst.js:26:13:26:42 | "http:/ ... tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:28:5:28:44 | request ... ainted) | The $@ of this request depends on $@. | tst.js:28:13:28:43 | "http:/ ... tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref new file mode 100644 index 000000000000..fcb4e41daf88 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref @@ -0,0 +1 @@ +Security/CWE-918/RequestForgery.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-918/tst.js b/javascript/ql/test/query-tests/Security/CWE-918/tst.js new file mode 100644 index 000000000000..b502e56bd12f --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/tst.js @@ -0,0 +1,31 @@ +import request from 'request'; +import requestPromise from 'request-promise'; +import superagent from 'superagent'; +import http from 'http'; +import express from 'express'; +import axios from 'axios'; +import got from 'got'; +import nodeFetch from 'node-fetch'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var tainted = url.parse(req.url, true).query.url; + + request("example.com"); // OK + + request(tainted); // NOT OK + + request.get(tainted); // NOT OK + + var options = {}; + options.url = tainted; + request(options); // NOT OK + + request("http://" + tainted); // NOT OK + + request("http://example.com" + tainted); // NOT OK + + request("http://example.com/" + tainted); // NOT OK + + request("http://example.com/?" + tainted); // OK +})