mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
Merge branch 'main' into aegilops/js/insecure-helmet-middleware
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
## 0.8.16
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.15
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
3
javascript/ql/src/change-notes/released/0.8.16.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.16.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.16
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.15
|
||||
lastReleaseVersion: 0.8.16
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const tar = require("tar");
|
||||
|
||||
tar.x({
|
||||
file: tarFileName,
|
||||
strip: 1,
|
||||
C: 'some-dir',
|
||||
maxReadSize: 16 * 1024 * 1024 // 16 MB
|
||||
})
|
||||
@@ -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);
|
||||
179
javascript/ql/src/experimental/semmle/javascript/FormParsers.qll
Normal file
179
javascript/ql/src/experimental/semmle/javascript/FormParsers.qll
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 0.8.15
|
||||
version: 0.8.17-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
Reference in New Issue
Block a user