Merge pull request #1095 from xiemaisi/js/base64

Approved by esben-semmle
This commit is contained in:
semmle-qlci
2019-03-14 11:58:50 +00:00
committed by GitHub
18 changed files with 332 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ import semmle.javascript.Aliases
import semmle.javascript.AMD
import semmle.javascript.AST
import semmle.javascript.BasicBlocks
import semmle.javascript.Base64
import semmle.javascript.CFG
import semmle.javascript.Classes
import semmle.javascript.Closure

View File

@@ -0,0 +1,200 @@
/**
* Provides classes and predicates for working with base64 encoders and decoders.
*/
import javascript
module Base64 {
/** A call to a base64 encoder. */
class Encode extends DataFlow::Node {
Encode::Range encode;
Encode() { this = encode }
/** Gets the input passed to the encoder. */
DataFlow::Node getInput() { result = encode.getInput() }
/** Gets the base64-encoded output of the encoder. */
DataFlow::Node getOutput() { result = encode.getOutput() }
}
module Encode {
/**
* A data flow node that should be considered a call to a base64 encoder.
*
* New base64 encoding functions can be supported by extending this class.
*/
abstract class Range extends DataFlow::Node {
/** Gets the input passed to the encoder. */
abstract DataFlow::Node getInput();
/** Gets the base64-encoded output of the encoder. */
abstract DataFlow::Node getOutput();
}
}
/** A call to a base64 decoder. */
class Decode extends DataFlow::Node {
Decode::Range encode;
Decode() { this = encode }
/** Gets the base64-encoded input passed to the decoder. */
DataFlow::Node getInput() { result = encode.getInput() }
/** Gets the output of the decoder. */
DataFlow::Node getOutput() { result = encode.getOutput() }
}
module Decode {
/**
* A data flow node that should be considered a call to a base64 decoder.
*
* New base64 decoding functions can be supported by extending this class.
*/
abstract class Range extends DataFlow::Node {
/** Gets the base64-encoded input passed to the decoder. */
abstract DataFlow::Node getInput();
/** Gets the output of the decoder. */
abstract DataFlow::Node getOutput();
}
}
/**
* A base64 decoding step, viewed as a taint-propagating data flow edge.
*
* Note that we currently do not model base64 encoding as a taint-propagating data flow edge
* to avoid false positives.
*/
private class Base64DecodingStep extends TaintTracking::AdditionalTaintStep {
Decode dec;
Base64DecodingStep() { this = dec }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = dec.getInput() and
succ = dec.getOutput()
}
}
}
/** A call to `btoa`. */
private class Btoa extends Base64::Encode::Range, DataFlow::CallNode {
Btoa() { this = DataFlow::globalVarRef("btoa").getACall() }
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/** A call to `atob`. */
private class Atob extends Base64::Decode::Range, DataFlow::CallNode {
Atob() { this = DataFlow::globalVarRef("atob").getACall() }
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to `Buffer.prototype.toString` with encoding `base64`, approximated by
* looking for calls to `toString` where the first argument is the string `"base64"`.
*/
private class Buffer_toString extends Base64::Encode::Range, DataFlow::MethodCallNode {
Buffer_toString() {
getMethodName() = "toString" and
getArgument(0).mayHaveStringValue("base64")
}
override DataFlow::Node getInput() { result = getReceiver() }
override DataFlow::Node getOutput() { result = this }
}
/** A call to `Buffer.from` with encoding `base64`. */
private class Buffer_from extends Base64::Decode::Range, DataFlow::CallNode {
Buffer_from() {
this = DataFlow::globalVarRef("Buffer").getAMemberCall("from") and
getArgument(1).mayHaveStringValue("base64")
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to a base64 encoding function from one of the npm packages
* `base-64`, `js-base64`, `Base64`, or `base64-js`.
*/
private class NpmBase64Encode extends Base64::Encode::Range, DataFlow::CallNode {
NpmBase64Encode() {
exists(DataFlow::SourceNode enc |
enc = DataFlow::moduleImport("b64u") or
enc = DataFlow::moduleImport("b64url") or
enc = DataFlow::moduleImport("btoa") or
enc = DataFlow::moduleMember("Base64", "btoa") or
enc = DataFlow::moduleMember("abab", "btoa") or
enc = DataFlow::moduleMember("b2a", "btoa") or
enc = DataFlow::moduleMember("b64-lite", "btoa") or
enc = DataFlow::moduleMember("b64-lite", "toBase64") or
enc = DataFlow::moduleMember("b64u", "encode") or
enc = DataFlow::moduleMember("b64u", "toBase64") or
enc = DataFlow::moduleMember("b64u-lite", "toBase64Url") or
enc = DataFlow::moduleMember("b64u-lite", "toBinaryString") or
enc = DataFlow::moduleMember("b64url", "encode") or
enc = DataFlow::moduleMember("b64url", "toBase64") or
enc = DataFlow::moduleMember("base-64", "encode") or
enc = DataFlow::moduleMember("base64-js", "toByteArray") or
enc = DataFlow::moduleMember("base64-url", "encode") or
enc = DataFlow::moduleMember("base64url", "encode") or
enc = DataFlow::moduleMember("base64url", "toBase64") or
enc = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("encode") or
enc = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("encodeURI") or
enc = DataFlow::moduleMember("urlsafe-base64", "encode")
|
this = enc.getACall()
)
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to a base64 decoding function from one of the npm packages
* `base-64`, `js-base64`, `Base64`, or `base64-js`.
*/
private class NpmBase64Decode extends Base64::Decode::Range, DataFlow::CallNode {
NpmBase64Decode() {
exists(DataFlow::SourceNode dec |
dec = DataFlow::moduleImport("atob") or
dec = DataFlow::moduleMember("Base64", "atob") or
dec = DataFlow::moduleMember("abab", "atob") or
dec = DataFlow::moduleMember("b2a", "atob") or
dec = DataFlow::moduleMember("b64-lite", "atob") or
dec = DataFlow::moduleMember("b64-lite", "fromBase64") or
dec = DataFlow::moduleMember("b64u", "decode") or
dec = DataFlow::moduleMember("b64u", "fromBase64") or
dec = DataFlow::moduleMember("b64u-lite", "fromBase64Url") or
dec = DataFlow::moduleMember("b64u-lite", "fromBinaryString") or
dec = DataFlow::moduleMember("b64url", "decode") or
dec = DataFlow::moduleMember("b64url", "fromBase64") or
dec = DataFlow::moduleMember("base-64", "decode") or
dec = DataFlow::moduleMember("base64-js", "fromByteArray") or
dec = DataFlow::moduleMember("base64-url", "decode") or
dec = DataFlow::moduleMember("base64url", "decode") or
dec = DataFlow::moduleMember("base64url", "fromBase64") or
dec = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("decode") or
dec = DataFlow::moduleMember("urlsafe-base64", "decode")
|
this = dec.getACall()
)
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}

View File

@@ -443,7 +443,7 @@ module TaintTracking {
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
)
or
// `(encode|decode)URI(Component)?` and `escape` propagate taint
// `(encode|decode)URI(Component)?` propagate taint
exists(DataFlow::CallNode c, string name |
this = c and
c = DataFlow::globalVarRef(name).getACall() and

View File

@@ -1,5 +1,5 @@
/**
* Provides predicates for reasoning about DOM types.
* Provides predicates for reasoning about DOM types and methods.
*/
import javascript

View File

@@ -0,0 +1,6 @@
var Base64 = require("Base64");
function roundtrip(data) {
var encoded = Base64.btoa(data);
return Base64.atob(encoded);
}

View File

@@ -0,0 +1,9 @@
function roundtrip(data) {
var encoded = Buffer.from(data, 'base64');
return encoded.toString('base64');
}
function roundtrip2(data) {
var encoded = Buffer.from(data, 'hex');
return encoded.toString('hex');
}

View File

@@ -0,0 +1,10 @@
import javascript
query Base64::Decode test_Decode() {
any()
}
query predicate test_Decode_input_output(Base64::Decode decode, DataFlow::Node input, DataFlow::Node output) {
input = decode.getInput() and
output = decode.getOutput()
}

View File

@@ -0,0 +1,10 @@
import javascript
query Base64::Encode test_Encode() {
any()
}
query predicate test_Encode_input_output(Base64::Encode encode, DataFlow::Node input, DataFlow::Node output) {
input = encode.getInput() and
output = encode.getOutput()
}

View File

@@ -0,0 +1,6 @@
var base64 = require('base-64');
function roundtrip(data) {
var encoded = base64.encode(data);
return base64.decode(encoded);
}

View File

@@ -0,0 +1,6 @@
var base64 = require('base64-js');
function roundtrip(data) {
var encoded = base64.toByteArray(data);
return base64.fromByteArray(encoded);
}

View File

@@ -0,0 +1,4 @@
function roundtrip(data) {
var encoded = btoa(data);
return atob(encoded);
}

View File

@@ -0,0 +1,11 @@
var base64 = require('js-base64').Base64;
function roundtrip1(data) {
var encoded = base64.encode(data);
return base64.decode(encoded);
}
function roundtrip2(data) {
var encoded = base64.encodeURI(data);
return base64.decode(encoded);
}

View File

@@ -0,0 +1,11 @@
import { Base64 as base64 } from 'js-base64';
function roundtrip1(data) {
var encoded = base64.encode(data);
return base64.decode(encoded);
}
function roundtrip2(data) {
var encoded = base64.encodeURI(data);
return base64.decode(encoded);
}

View File

@@ -0,0 +1,40 @@
test_Encode
| Base64.js:4:17:4:33 | Base64.btoa(data) |
| Buffer.js:3:10:3:35 | encoded ... ase64') |
| base64-js.js:4:17:4:40 | base64. ... y(data) |
| base-64.js:4:17:4:35 | base64.encode(data) |
| dom.js:2:17:2:26 | btoa(data) |
| js-base64.js:4:17:4:35 | base64.encode(data) |
| js-base64.js:9:17:9:38 | base64. ... I(data) |
| js-base64b.js:4:17:4:35 | base64.encode(data) |
| js-base64b.js:9:17:9:38 | base64. ... I(data) |
test_Encode_input_output
| Base64.js:4:17:4:33 | Base64.btoa(data) | Base64.js:4:29:4:32 | data | Base64.js:4:17:4:33 | Base64.btoa(data) |
| Buffer.js:3:10:3:35 | encoded ... ase64') | Buffer.js:3:10:3:16 | encoded | Buffer.js:3:10:3:35 | encoded ... ase64') |
| base64-js.js:4:17:4:40 | base64. ... y(data) | base64-js.js:4:36:4:39 | data | base64-js.js:4:17:4:40 | base64. ... y(data) |
| base-64.js:4:17:4:35 | base64.encode(data) | base-64.js:4:31:4:34 | data | base-64.js:4:17:4:35 | base64.encode(data) |
| dom.js:2:17:2:26 | btoa(data) | dom.js:2:22:2:25 | data | dom.js:2:17:2:26 | btoa(data) |
| js-base64.js:4:17:4:35 | base64.encode(data) | js-base64.js:4:31:4:34 | data | js-base64.js:4:17:4:35 | base64.encode(data) |
| js-base64.js:9:17:9:38 | base64. ... I(data) | js-base64.js:9:34:9:37 | data | js-base64.js:9:17:9:38 | base64. ... I(data) |
| js-base64b.js:4:17:4:35 | base64.encode(data) | js-base64b.js:4:31:4:34 | data | js-base64b.js:4:17:4:35 | base64.encode(data) |
| js-base64b.js:9:17:9:38 | base64. ... I(data) | js-base64b.js:9:34:9:37 | data | js-base64b.js:9:17:9:38 | base64. ... I(data) |
test_Decode
| Base64.js:5:10:5:29 | Base64.atob(encoded) |
| Buffer.js:2:17:2:43 | Buffer. ... ase64') |
| base64-js.js:5:10:5:38 | base64. ... ncoded) |
| base-64.js:5:10:5:31 | base64. ... ncoded) |
| dom.js:3:10:3:22 | atob(encoded) |
| js-base64.js:5:10:5:31 | base64. ... ncoded) |
| js-base64.js:10:10:10:31 | base64. ... ncoded) |
| js-base64b.js:5:10:5:31 | base64. ... ncoded) |
| js-base64b.js:10:10:10:31 | base64. ... ncoded) |
test_Decode_input_output
| Base64.js:5:10:5:29 | Base64.atob(encoded) | Base64.js:5:22:5:28 | encoded | Base64.js:5:10:5:29 | Base64.atob(encoded) |
| Buffer.js:2:17:2:43 | Buffer. ... ase64') | Buffer.js:2:29:2:32 | data | Buffer.js:2:17:2:43 | Buffer. ... ase64') |
| base64-js.js:5:10:5:38 | base64. ... ncoded) | base64-js.js:5:31:5:37 | encoded | base64-js.js:5:10:5:38 | base64. ... ncoded) |
| base-64.js:5:10:5:31 | base64. ... ncoded) | base-64.js:5:24:5:30 | encoded | base-64.js:5:10:5:31 | base64. ... ncoded) |
| dom.js:3:10:3:22 | atob(encoded) | dom.js:3:15:3:21 | encoded | dom.js:3:10:3:22 | atob(encoded) |
| js-base64.js:5:10:5:31 | base64. ... ncoded) | js-base64.js:5:24:5:30 | encoded | js-base64.js:5:10:5:31 | base64. ... ncoded) |
| js-base64.js:10:10:10:31 | base64. ... ncoded) | js-base64.js:10:24:10:30 | encoded | js-base64.js:10:10:10:31 | base64. ... ncoded) |
| js-base64b.js:5:10:5:31 | base64. ... ncoded) | js-base64b.js:5:24:5:30 | encoded | js-base64b.js:5:10:5:31 | base64. ... ncoded) |
| js-base64b.js:10:10:10:31 | base64. ... ncoded) | js-base64b.js:10:24:10:30 | encoded | js-base64b.js:10:10:10:31 | base64. ... ncoded) |

View File

@@ -0,0 +1,2 @@
import Encode
import Decode

View File

@@ -51,6 +51,10 @@ nodes
| tst.js:17:21:17:42 | documen ... on.hash |
| tst.js:20:30:20:46 | document.location |
| tst.js:20:30:20:51 | documen ... on.hash |
| tst.js:23:6:23:46 | atob(do ... ing(1)) |
| tst.js:23:11:23:27 | document.location |
| tst.js:23:11:23:32 | documen ... on.hash |
| tst.js:23:11:23:45 | documen ... ring(1) |
edges
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
| angularjs.js:13:23:13:30 | location | angularjs.js:13:23:13:37 | location.search |
@@ -86,6 +90,9 @@ edges
| tst.js:14:10:14:33 | documen ... .search | tst.js:14:10:14:74 | documen ... , "$1") |
| tst.js:17:21:17:37 | document.location | tst.js:17:21:17:42 | documen ... on.hash |
| tst.js:20:30:20:46 | document.location | tst.js:20:30:20:51 | documen ... on.hash |
| tst.js:23:11:23:27 | document.location | tst.js:23:11:23:32 | documen ... on.hash |
| tst.js:23:11:23:32 | documen ... on.hash | tst.js:23:11:23:45 | documen ... ring(1) |
| tst.js:23:11:23:45 | documen ... ring(1) | tst.js:23:6:23:46 | atob(do ... ing(1)) |
#select
| angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:10:22:10:29 | location | User-provided value |
| angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:30 | location | angularjs.js:13:23:13:37 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:13:23:13:30 | location | User-provided value |
@@ -112,3 +119,4 @@ edges
| tst.js:14:10:14:74 | documen ... , "$1") | tst.js:14:10:14:26 | document.location | tst.js:14:10:14:74 | documen ... , "$1") | $@ flows to here and is interpreted as code. | tst.js:14:10:14:26 | document.location | User-provided value |
| tst.js:17:21:17:42 | documen ... on.hash | tst.js:17:21:17:37 | document.location | tst.js:17:21:17:42 | documen ... on.hash | $@ flows to here and is interpreted as code. | tst.js:17:21:17:37 | document.location | User-provided value |
| tst.js:20:30:20:51 | documen ... on.hash | tst.js:20:30:20:46 | document.location | tst.js:20:30:20:51 | documen ... on.hash | $@ flows to here and is interpreted as code. | tst.js:20:30:20:46 | document.location | User-provided value |
| tst.js:23:6:23:46 | atob(do ... ing(1)) | tst.js:23:11:23:27 | document.location | tst.js:23:6:23:46 | atob(do ... ing(1)) | $@ flows to here and is interpreted as code. | tst.js:23:11:23:27 | document.location | User-provided value |

View File

@@ -18,3 +18,6 @@ WebAssembly.compile(document.location.hash);
// NOT OK
WebAssembly.compileStreaming(document.location.hash);
// NOT OK
eval(atob(document.location.hash.substring(1)));