JavaScript: Add models for popular base64 transcoders.

This commit is contained in:
Max Schaefer
2019-03-07 09:19:17 +00:00
parent 6baf52614e
commit 28d8011bcf
15 changed files with 302 additions and 0 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,175 @@
/**
* Provides classes and predicates for working with popular 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.from` with encoding `base64`. */
private class Buffer_from extends Base64::Encode::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 `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::Decode::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 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(string mod, string meth |
mod = "base-64" and meth = "encode"
or
mod = "Base64" and meth = "btoa"
or
mod = "base64-js" and meth = "toByteArray"
|
this = DataFlow::moduleMember(mod, meth).getACall()
)
or
exists(string meth | meth = "encode" or meth = "encodeURI" |
this = DataFlow::moduleMember("js-base64", "Base64").getAMemberCall(meth)
)
}
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(string mod, string meth |
mod = "base-64" and meth = "decode"
or
mod = "Base64" and meth = "atob"
or
mod = "base64-js" and meth = "fromByteArray"
|
this = DataFlow::moduleMember(mod, meth).getACall()
)
or
this = DataFlow::moduleMember("js-base64", "Base64").getAMemberCall("decode")
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}

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,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,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,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) |
| base64-js.js:4:17:4:40 | base64. ... y(data) |
| base64.js:2:17:2:43 | Buffer. ... ase64') |
| 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) |
| 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) |
| base64.js:2:17:2:43 | Buffer. ... ase64') | base64.js:2:29:2:32 | data | base64.js:2:17:2:43 | Buffer. ... ase64') |
| 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) |
| base64-js.js:5:10:5:38 | base64. ... ncoded) |
| base64.js:3:10:3:35 | encoded ... ase64') |
| 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) |
| 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) |
| base64.js:3:10:3:35 | encoded ... ase64') | base64.js:3:10:3:16 | encoded | base64.js:3:10:3:35 | encoded ... ase64') |
| 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)));