mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JavaScript: Add models for popular base64 transcoders.
This commit is contained in:
@@ -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
|
||||
|
||||
175
javascript/ql/src/semmle/javascript/Base64.qll
Normal file
175
javascript/ql/src/semmle/javascript/Base64.qll
Normal 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 }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
var Base64 = require("Base64");
|
||||
|
||||
function roundtrip(data) {
|
||||
var encoded = Base64.btoa(data);
|
||||
return Base64.atob(encoded);
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
var base64 = require('base-64');
|
||||
|
||||
function roundtrip(data) {
|
||||
var encoded = base64.encode(data);
|
||||
return base64.decode(encoded);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
var base64 = require('base64-js');
|
||||
|
||||
function roundtrip(data) {
|
||||
var encoded = base64.toByteArray(data);
|
||||
return base64.fromByteArray(encoded);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
function roundtrip(data) {
|
||||
var encoded = btoa(data);
|
||||
return atob(encoded);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) |
|
||||
@@ -0,0 +1,2 @@
|
||||
import Encode
|
||||
import Decode
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user